diff --git a/structuralcodes/core/base/__init__.py b/structuralcodes/core/base/__init__.py index 54f8547c..a7efac0a 100644 --- a/structuralcodes/core/base/__init__.py +++ b/structuralcodes/core/base/__init__.py @@ -1,13 +1,14 @@ """Abstract base classes for the class hierarchy.""" from ._constitutive_law import ConstitutiveLaw -from ._geometry import Geometry +from ._geometry import Geometry, _GroupMixin from ._material import Material from ._section import Section, SectionCalculator __all__ = [ 'ConstitutiveLaw', 'Geometry', + '_GroupMixin', 'Material', 'Section', 'SectionCalculator', diff --git a/structuralcodes/core/base/_constitutive_law.py b/structuralcodes/core/base/_constitutive_law.py index badca37e..e61af3d2 100644 --- a/structuralcodes/core/base/_constitutive_law.py +++ b/structuralcodes/core/base/_constitutive_law.py @@ -13,13 +13,15 @@ class ConstitutiveLaw(abc.ABC): """Abstract base class for constitutive laws.""" __materials__: t.Tuple[str] = () - constitutive_law_counter: t.ClassVar[int] = 0 + _constitutive_law_counter: t.ClassVar[int] = 0 + id: int - def __init__(self, name: t.Optional[str] = None) -> None: + def __init__( + self, name: t.Optional[str] = None, base_name: str = 'ConstitutiveLaw' + ) -> None: """Initialize a ConstitutiveLaw object.""" - self.id = self.constitutive_law_counter - self._name = name if name is not None else f'ConstitutiveLaw_{self.id}' - self._increase_global_counter() + self.id = ConstitutiveLaw.return_global_counter_and_increase() + self._name = name if name is not None else f'{base_name}_{self.id}' @property def name(self): @@ -28,7 +30,14 @@ def name(self): @classmethod def _increase_global_counter(cls): - cls.constitutive_law_counter += 1 + cls._constitutive_law_counter += 1 + + @classmethod + def return_global_counter_and_increase(cls): + """Returns the current counter and increases it by one.""" + counter = cls._constitutive_law_counter + cls._increase_global_counter() + return counter @abc.abstractmethod def get_stress( diff --git a/structuralcodes/core/base/_geometry.py b/structuralcodes/core/base/_geometry.py index 0ee62348..3211c0ce 100644 --- a/structuralcodes/core/base/_geometry.py +++ b/structuralcodes/core/base/_geometry.py @@ -8,13 +8,57 @@ from ._material import Material +class _GroupMixin: + """A mixin class for handling functionality related to group labels.""" + + _group_label: t.Optional[str] = None + + @property + def group_label(self): + """Returns the group_label.""" + return self._group_label + + def _group_matches( + self, pattern: str, *, case_sensitive: bool = True + ) -> bool: + """Checks if the group_label matches a pattern. + + Arguments: + pattern (str): the string pattern to be checked + + Keyword Arguments: + case_sensitive (bool, optional): if True (default) the check is + case sensitive. + + Returns: + (bool): Returns True if the group_label matches the pattern. + + Note: + The matching permits to use: + - "*" any chars + - "?" single char + - "[abc]" character set + + Examples: + >>> geo.group_matches("nametos*") + >>> geo.group_matches("*pier*") + >>> geo.group_matches("Abutment??", case_senstive=False) + """ + if self.group_label is None: + return False + if not case_sensitive: + return fnmatchcase(self.group_label.casefold(), pattern.casefold()) + return fnmatchcase(self.group_label, pattern) + + class Geometry: """Base class for a geometry object.""" - section_counter: t.ClassVar[int] = 0 + _geometry_counter: t.ClassVar[int] = 0 + id: int def __init__( - self, name: t.Optional[str] = None, group_label: t.Optional[str] = None + self, name: t.Optional[str] = None, base_name: str = 'Geometry' ) -> None: """Initializes a geometry object. @@ -23,34 +67,26 @@ def __init__( Arguments: name (Optional(str)): The name to be given to the object. - group_label (Optional(str)): A label for grouping several objects. + base_name (str): If name is not given, use this argument together + with a global counter to create a unique name for the geometry. """ - if name is not None: - self._name = name - else: - counter = Geometry.return_global_counter_and_increase() - self._name = f'Geometry_{counter}' - self._group_label = group_label + self.id = Geometry.return_global_counter_and_increase() + self._name = name if name is not None else f'{base_name}_{self.id}' @property def name(self): """Returns the name of the Geometry.""" return self._name - @property - def group_label(self): - """Returns the group_label fo the Geometry.""" - return self._group_label - @classmethod def _increase_global_counter(cls): """Increases the global counter by one.""" - cls.section_counter += 1 + cls._geometry_counter += 1 @classmethod def return_global_counter_and_increase(cls): """Returns the current counter and increases it by one.""" - counter = cls.section_counter + counter = cls._geometry_counter cls._increase_global_counter() return counter @@ -93,35 +129,3 @@ def _name_matches( if not case_sensitive: return fnmatchcase(self.name.casefold(), pattern.casefold()) return fnmatchcase(self.name, pattern) - - def _group_matches( - self, pattern: str, *, case_sensitive: bool = True - ) -> bool: - """Checks if the group_label matches a pattern. - - Arguments: - pattern (str): the string pattern to be checked - - Keyword Arguments: - case_sensitive (bool, optional): if True (default) the check is - case sensitive. - - Returns: - (bool): Returns True if the group_label matches the pattern. - - Note: - The matching permits to use: - - "*" any chars - - "?" single char - - "[abc]" character set - - Examples: - >>> geo.group_matches("nametos*") - >>> geo.group_matches("*pier*") - >>> geo.group_matches("Abutment??", case_senstive=False) - """ - if self.group_label is None: - return False - if not case_sensitive: - return fnmatchcase(self.group_label.casefold(), pattern.casefold()) - return fnmatchcase(self.group_label, pattern) diff --git a/structuralcodes/core/base/_material.py b/structuralcodes/core/base/_material.py index 27dd7a23..417bc4d8 100644 --- a/structuralcodes/core/base/_material.py +++ b/structuralcodes/core/base/_material.py @@ -15,6 +15,8 @@ class Material(abc.ABC): _initial_strain: t.Optional[float] = None _initial_stress: t.Optional[float] = None _strain_compatibility: t.Optional[bool] = None + _material_counter: t.ClassVar[int] = 0 + id: int def __init__( self, @@ -23,6 +25,7 @@ def __init__( initial_stress: t.Optional[float] = None, strain_compatibility: t.Optional[bool] = None, name: t.Optional[str] = None, + base_name: str = 'Material', ) -> None: """Initializes an instance of a new material. @@ -37,7 +40,9 @@ def __init__( True, the material deforms with the geometry. If False, the stress in the material upon loading is kept constant corresponding to the initial strain. - name (Optional[str]): descriptive name of the material + name (Optional[str]): Descriptive name of the material + base_name (str): If name is not given, use this argument together + with a global counter to create a unique name for the material. Raise: ValueError: if both initial_strain and initial_stress are provided @@ -50,7 +55,8 @@ def __init__( self._initial_strain = initial_strain self._initial_stress = initial_stress self._strain_compatibility = strain_compatibility - self._name = name if name is not None else 'Material' + self.id = Material.return_global_counter_and_increase() + self._name = name if name is not None else f'{base_name}_{self.id}' @property def constitutive_law(self) -> ConstitutiveLaw: @@ -62,6 +68,17 @@ def name(self): """Returns the name of the material.""" return self._name + @classmethod + def _increase_global_counter(cls): + cls._material_counter += 1 + + @classmethod + def return_global_counter_and_increase(cls): + """Returns the current counter and increases it by one.""" + counter = cls._material_counter + cls._increase_global_counter() + return counter + @property def density(self): """Returns the density of the material in kg/m3.""" diff --git a/structuralcodes/core/base/_section.py b/structuralcodes/core/base/_section.py index f57aae69..8a147f7d 100644 --- a/structuralcodes/core/base/_section.py +++ b/structuralcodes/core/base/_section.py @@ -15,14 +15,16 @@ class Section(abc.ABC): """ geometry: Geometry - section_counter: t.ClassVar[int] = 0 + _section_counter: t.ClassVar[int] = 0 + id: int section_calculator: SectionCalculator - def __init__(self, name: t.Optional[str] = None) -> None: + def __init__( + self, name: t.Optional[str] = None, base_name: str = 'Section' + ) -> None: """Initialize a Section object.""" - self.id = self.section_counter - self._name = name if name is not None else f'Section_{self.id}' - self._increase_global_counter() + self.id = Section.return_global_counter_and_increase() + self._name = name if name is not None else f'{base_name}_{self.id}' @property def name(self): @@ -31,7 +33,14 @@ def name(self): @classmethod def _increase_global_counter(cls): - cls.section_counter += 1 + cls._section_counter += 1 + + @classmethod + def return_global_counter_and_increase(cls): + """Returns the current counter and increases it by one.""" + counter = cls._section_counter + cls._increase_global_counter() + return counter class SectionCalculator(abc.ABC): diff --git a/structuralcodes/geometry/_geometry.py b/structuralcodes/geometry/_geometry.py index d5eb3455..98d3286f 100644 --- a/structuralcodes/geometry/_geometry.py +++ b/structuralcodes/geometry/_geometry.py @@ -23,7 +23,7 @@ from structuralcodes.materials.basic import ElasticMaterial from structuralcodes.materials.concrete import Concrete -from ..core.base import Geometry, Material +from ..core.base import Geometry, Material, _GroupMixin def _mirror_about_axis_matrix(axis: LineString) -> np.ndarray: @@ -59,7 +59,7 @@ def _mirror_about_axis_matrix(axis: LineString) -> np.ndarray: return T_inv @ R_inv @ M @ R @ T -class PointGeometry(Geometry): +class PointGeometry(_GroupMixin, Geometry): """Class for a point geometry with material. Basically it is a wrapper for shapely Point including the material (and @@ -88,7 +88,8 @@ def __init__( group_label (Optional(str)): A label for grouping several objects (default is None). """ - super().__init__(name, group_label) + super().__init__(name) + self._group_label = group_label # I check if point is a shapely Point or an ArrayLike object if not isinstance(point, Point): # It is an ArrayLike object -> create the Point given the @@ -316,7 +317,7 @@ def create_line_point_angle( return LineString([(x1, y1), (x2, y2)]) -class SurfaceGeometry(Geometry): +class SurfaceGeometry(_GroupMixin, Geometry): """Class for a surface geometry with material. Basically it is a wrapper for shapely polygon including the material (and @@ -343,7 +344,8 @@ def __init__( name (Optional(str)): The name to be given to the object. group_label (Optional(str)): A label for grouping several objects. """ - super().__init__(name=name, group_label=group_label) + super().__init__(name=name) + self._group_label = group_label # Check if inputs are of the correct type, otherwise return error if not isinstance(poly, Polygon): raise TypeError( diff --git a/structuralcodes/materials/basic/_elastic.py b/structuralcodes/materials/basic/_elastic.py index 25a74aeb..4e0fb7d1 100644 --- a/structuralcodes/materials/basic/_elastic.py +++ b/structuralcodes/materials/basic/_elastic.py @@ -49,7 +49,8 @@ def __init__( initial_strain=initial_strain, initial_stress=initial_stress, strain_compatibility=strain_compatibility, - name=name if name else 'ElasticMaterial', + name=name, + base_name='ElasticMaterial', ) self._E = E self._ultimate_strain = ultimate_strain diff --git a/structuralcodes/materials/basic/_elasticplastic.py b/structuralcodes/materials/basic/_elasticplastic.py index b1e39b49..5152fd61 100644 --- a/structuralcodes/materials/basic/_elasticplastic.py +++ b/structuralcodes/materials/basic/_elasticplastic.py @@ -48,7 +48,8 @@ def __init__( initial_strain=initial_strain, initial_stress=initial_stress, strain_compatibility=strain_compatibility, - name=name if name else 'ElasticPlasticMaterial', + name=name, + base_name='ElasticPlasticMaterial', ) self._E = E self._fy = fy diff --git a/structuralcodes/materials/basic/_generic.py b/structuralcodes/materials/basic/_generic.py index 1e3b4b28..114ddf1f 100644 --- a/structuralcodes/materials/basic/_generic.py +++ b/structuralcodes/materials/basic/_generic.py @@ -37,7 +37,8 @@ def __init__( initial_strain=initial_strain, initial_stress=initial_stress, strain_compatibility=strain_compatibility, - name=name if name else 'GenericMaterial', + name=name, + base_name='GenericMaterial', ) self._constitutive_law = constitutive_law self._apply_initial_strain() diff --git a/structuralcodes/materials/concrete/_concrete.py b/structuralcodes/materials/concrete/_concrete.py index 8d2d9b75..0119fc80 100644 --- a/structuralcodes/materials/concrete/_concrete.py +++ b/structuralcodes/materials/concrete/_concrete.py @@ -26,13 +26,13 @@ def __init__( strain_compatibility: t.Optional[bool] = None, ) -> None: """Initializes an abstract concrete material.""" - name = name if name is not None else 'Concrete' super().__init__( density=density, initial_strain=initial_strain, initial_stress=initial_stress, strain_compatibility=strain_compatibility, name=name, + base_name='Concrete', ) self._fck = abs(fck) diff --git a/structuralcodes/materials/constitutive_laws/_bilinearcompression.py b/structuralcodes/materials/constitutive_laws/_bilinearcompression.py index d6e42620..3e30ac85 100644 --- a/structuralcodes/materials/constitutive_laws/_bilinearcompression.py +++ b/structuralcodes/materials/constitutive_laws/_bilinearcompression.py @@ -34,8 +34,7 @@ def __init__( eps_cu (float): Ultimate strain (pure number). name (str): A descriptive name for the constitutive law. """ - name = name if name is not None else 'BilinearCompressionLaw' - super().__init__(name=name) + super().__init__(name=name, base_name='BilinearCompressionLaw') self._fc = -abs(fc) self._eps_c = -abs(eps_c) self._eps_cu = -abs(eps_cu) diff --git a/structuralcodes/materials/constitutive_laws/_elastic.py b/structuralcodes/materials/constitutive_laws/_elastic.py index d0852572..88fa26cc 100644 --- a/structuralcodes/materials/constitutive_laws/_elastic.py +++ b/structuralcodes/materials/constitutive_laws/_elastic.py @@ -37,8 +37,7 @@ def __init__( and positive strains. If a tuple is provided, it should be given as (negative, positive). Default value = None. """ - name = name if name is not None else 'ElasticLaw' - super().__init__(name=name) + super().__init__(name=name, base_name='ElasticLaw') self._E = E if E <= 0: raise ValueError('Elastic modulus should be greater than 0') diff --git a/structuralcodes/materials/constitutive_laws/_elasticplastic.py b/structuralcodes/materials/constitutive_laws/_elasticplastic.py index 84c53470..24f449c6 100644 --- a/structuralcodes/materials/constitutive_laws/_elasticplastic.py +++ b/structuralcodes/materials/constitutive_laws/_elasticplastic.py @@ -37,8 +37,7 @@ def __init__( eps_su (float, optional): The ultimate strain. name (str, optional): A descriptive name for the constitutive law. """ - name = name if name is not None else 'ElasticPlasticLaw' - super().__init__(name=name) + super().__init__(name=name, base_name='ElasticPlasticLaw') if E > 0: self._E = E else: diff --git a/structuralcodes/materials/constitutive_laws/_initial_strain.py b/structuralcodes/materials/constitutive_laws/_initial_strain.py index 076c2270..1166a12b 100644 --- a/structuralcodes/materials/constitutive_laws/_initial_strain.py +++ b/structuralcodes/materials/constitutive_laws/_initial_strain.py @@ -43,8 +43,7 @@ def __init__( is helpful for instance for modelling unbonded tendons. Default value True. """ - name = name if name is not None else 'InitialStrainLaw' - super().__init__(name=name) + super().__init__(name=name, base_name='InitialStrainLaw') if not isinstance(constitutive_law, ConstitutiveLaw): raise TypeError( f'Expected a ConstitutiveLaw instance, ' diff --git a/structuralcodes/materials/constitutive_laws/_parabolarectangle.py b/structuralcodes/materials/constitutive_laws/_parabolarectangle.py index bb255db8..8af21d54 100644 --- a/structuralcodes/materials/constitutive_laws/_parabolarectangle.py +++ b/structuralcodes/materials/constitutive_laws/_parabolarectangle.py @@ -40,8 +40,7 @@ def __init__( n (float): Exponent for the pre-peak branch. Default value = 2. name (str): A name for the constitutive law. """ - name = name if name is not None else 'ParabolaRectangleLaw' - super().__init__(name=name) + super().__init__(name=name, base_name='ParabolaRectangleLaw') self._fc = -abs(fc) self._eps_0 = -abs(eps_0) self._eps_u = -abs(eps_u) diff --git a/structuralcodes/materials/constitutive_laws/_parallel.py b/structuralcodes/materials/constitutive_laws/_parallel.py index 32d4a8b0..da4d1305 100644 --- a/structuralcodes/materials/constitutive_laws/_parallel.py +++ b/structuralcodes/materials/constitutive_laws/_parallel.py @@ -39,8 +39,7 @@ def __init__( weights (List[float], optional): List of weights for each constitutive law. If None (Default), all weights are set to 1. """ - name = name if name is not None else 'ParallelLaw' - super().__init__(name=name) + super().__init__(name=name, base_name='ParallelLaw') for idx, law in enumerate(constitutive_laws): if not isinstance(law, ConstitutiveLaw): raise TypeError( diff --git a/structuralcodes/materials/constitutive_laws/_popovics.py b/structuralcodes/materials/constitutive_laws/_popovics.py index 080a8372..cb9ede02 100644 --- a/structuralcodes/materials/constitutive_laws/_popovics.py +++ b/structuralcodes/materials/constitutive_laws/_popovics.py @@ -60,8 +60,7 @@ def __init__( If positive values are input for fc, eps_c and eps_cu are input, they will be assumed negative. """ - name = name if name is not None else 'PopovicsLaw' - super().__init__(name=name) + super().__init__(name=name, base_name='PopovicsLaw') self._fc = -abs(fc) self._eps_c = -abs(eps_c) self._eps_cu = -abs(eps_cu) diff --git a/structuralcodes/materials/constitutive_laws/_sargin.py b/structuralcodes/materials/constitutive_laws/_sargin.py index 4b5d6f13..0428083d 100644 --- a/structuralcodes/materials/constitutive_laws/_sargin.py +++ b/structuralcodes/materials/constitutive_laws/_sargin.py @@ -52,8 +52,7 @@ def __init__( If positive values are input for fc, eps_c1 and eps_cu1 are input, they will be assumed negative. """ - name = name if name is not None else 'SarginLaw' - super().__init__(name=name) + super().__init__(name=name, base_name='SarginLaw') self._fc = -abs(fc) self._eps_c1 = -abs(eps_c1) self._eps_cu1 = -abs(eps_cu1) diff --git a/structuralcodes/materials/constitutive_laws/_userdefined.py b/structuralcodes/materials/constitutive_laws/_userdefined.py index 78c1344e..6f950544 100644 --- a/structuralcodes/materials/constitutive_laws/_userdefined.py +++ b/structuralcodes/materials/constitutive_laws/_userdefined.py @@ -46,8 +46,7 @@ def __init__( after ultimate strain, 1: stress is mantained constant, 2: last tangent is used, 3: last secant is used. """ - name = name if name is not None else 'UserDefinedLaw' - super().__init__(name=name) + super().__init__(name=name, base_name='UserDefinedLaw') x = np.atleast_1d(np.asarray(x)) y = np.atleast_1d(np.asarray(y)) if len(x) != len(y): diff --git a/structuralcodes/materials/reinforcement/_reinforcement.py b/structuralcodes/materials/reinforcement/_reinforcement.py index d388c00c..19b6adbd 100644 --- a/structuralcodes/materials/reinforcement/_reinforcement.py +++ b/structuralcodes/materials/reinforcement/_reinforcement.py @@ -29,13 +29,13 @@ def __init__( strain_compatibility: t.Optional[bool] = None, ) -> None: """Initializes an abstract reinforcement material.""" - name = name if name is not None else 'Reinforcement' super().__init__( density=density, initial_strain=initial_strain, initial_stress=initial_stress, strain_compatibility=strain_compatibility, name=name, + base_name='Reinforcement', ) self._fyk = abs(fyk) diff --git a/structuralcodes/sections/_beam_section.py b/structuralcodes/sections/_beam_section.py index 07aa8936..b159c8ae 100644 --- a/structuralcodes/sections/_beam_section.py +++ b/structuralcodes/sections/_beam_section.py @@ -71,9 +71,7 @@ def __init__( SectionCalculator to customize the behaviour. See BeamSectionCalculator for available keyword arguments. """ - if name is None: - name = 'BeamSection' - super().__init__(name) + super().__init__(name=name, base_name='BeamSection') # Since only CompoundGeometry has the attribute geometries, # if a SurfaceGeometry is input, we create a CompoundGeometry # with only that geometry contained. After that all algorithms diff --git a/tests/test_geometry/test_geometry.py b/tests/test_geometry/test_geometry.py index f38c711b..8b1af426 100644 --- a/tests/test_geometry/test_geometry.py +++ b/tests/test_geometry/test_geometry.py @@ -68,7 +68,7 @@ def test_create_line_point_angle( # Test PointGeometry def test_point_geometry(): """Test creating a PointGeometry object.""" - Geometry.section_counter = 0 + Geometry._geometry_counter = 0 # Create a consitutive law to use constitutive_law_steel = ElasticPlastic(210000, 450) steel = ReinforcementMC2010( diff --git a/tests/test_materials/test_constitutive_laws.py b/tests/test_materials/test_constitutive_laws.py index c339a0e9..2ce4f51c 100644 --- a/tests/test_materials/test_constitutive_laws.py +++ b/tests/test_materials/test_constitutive_laws.py @@ -742,3 +742,180 @@ def test_parallel_get_ultimate_strain(E1, fy1, eps_su1, E2, fy2, eps_su2): expected_ult_strain = max(eps_su1, eps_su2) assert math.isclose(ult_strain[1], expected_ult_strain) + + +def test_name_bilinear_compression(): + """Test the name property of a bilinear compression law.""" + # Create a law with a given name + given_name = 'A law' + law = BilinearCompression( + fc=45, eps_c=1.75e-3, eps_cu=3.5e-3, name=given_name + ) + + assert law.name == given_name + + # Create a law with a default name + counter = BilinearCompression._constitutive_law_counter + law = BilinearCompression( + fc=45, + eps_c=1.75e-3, + eps_cu=3.5e-3, + ) + + assert law.name == f'BilinearCompressionLaw_{counter}' + + +def test_name_parabola_rectangle(): + """Test the name property of a parabola rectangle law.""" + # Create a law with a given name + given_name = 'A law' + law = ParabolaRectangle(fc=45, name=given_name) + + assert law.name == given_name + + # Create a law with a default name + counter = ParabolaRectangle._constitutive_law_counter + law = ParabolaRectangle( + fc=45, + ) + + assert law.name == f'ParabolaRectangleLaw_{counter}' + + +def test_name_popovics(): + """Test the name property of a popovics law.""" + # Create a law with a given name + given_name = 'A law' + law = Popovics(fc=45, name=given_name) + + assert law.name == given_name + + # Create a law with a default name + counter = Popovics._constitutive_law_counter + law = Popovics( + fc=45, + ) + + assert law.name == f'PopovicsLaw_{counter}' + + +def test_name_sargin(): + """Test the name property of a sargin law.""" + # Create a law with a given name + given_name = 'A law' + law = Sargin(fc=45, name=given_name) + + assert law.name == given_name + + # Create a law with a default name + counter = Sargin._constitutive_law_counter + law = Sargin( + fc=45, + ) + + assert law.name == f'SarginLaw_{counter}' + + +def test_name_elastic(): + """Test the name property of an elastic law.""" + # Create a law with a given name + given_name = 'A law' + law = Elastic(E=30000, name=given_name) + + assert law.name == given_name + + # Create a law with a default name + counter = Elastic._constitutive_law_counter + law = Elastic(E=30000) + + assert law.name == f'ElasticLaw_{counter}' + + +def test_name_elasticplastic(): + """Test the name property of an elastic plastic law.""" + # Create a law with a given name + given_name = 'A law' + law = ElasticPlastic(E=200000, fy=355, Eh=0, name=given_name) + + assert law.name == given_name + + # Create a law with a default name + counter = ElasticPlastic._constitutive_law_counter + law = ElasticPlastic(E=200000, fy=355, Eh=0) + + assert law.name == f'ElasticPlasticLaw_{counter}' + + +def test_name_initial_strain(): + """Test the name property of an initial strain law.""" + # Create a base constitutive law + base_law = Elastic(E=30000) + initial_strain = 1e-3 + + # Create a law with a given name + given_name = 'A law' + law = InitialStrain( + constitutive_law=base_law, + initial_strain=initial_strain, + name=given_name, + ) + + assert law.name == given_name + + # Create a law with a default name + counter = InitialStrain._constitutive_law_counter + law = InitialStrain( + constitutive_law=base_law, initial_strain=initial_strain + ) + + assert law.name == f'InitialStrainLaw_{counter}' + + +def test_name_parallel(): + """Test the name property of a parallel law.""" + # Create two base constitutive laws + base_law_1 = Elastic(E=30000) + base_law_2 = Elastic(E=30000) + + # Create a law with a given name + given_name = 'A law' + law = Parallel( + constitutive_laws=[base_law_1, base_law_2], + name=given_name, + ) + + assert law.name == given_name + + # Create a law with a default name + counter = Parallel._constitutive_law_counter + law = Parallel( + constitutive_laws=[base_law_1, base_law_2], + ) + + assert law.name == f'ParallelLaw_{counter}' + + +def test_name_userdefined(): + """Test the name property of a user defined law.""" + # Create arrays of strains and stresses + strains = np.linspace(-2e-3, 2e3) + stresses = 30e3 * strains + + # Create a law with a given name + given_name = 'A law' + law = UserDefined( + x=strains, + y=stresses, + name=given_name, + ) + + assert law.name == given_name + + # Create a law with a default name + counter = UserDefined._constitutive_law_counter + law = UserDefined( + x=strains, + y=stresses, + ) + + assert law.name == f'UserDefinedLaw_{counter}' diff --git a/tests/test_materials/test_materials_basic.py b/tests/test_materials/test_materials_basic.py index 51429861..c4ea7a1a 100644 --- a/tests/test_materials/test_materials_basic.py +++ b/tests/test_materials/test_materials_basic.py @@ -4,8 +4,12 @@ import pytest -from structuralcodes.materials.basic import ElasticMaterial, GenericMaterial -from structuralcodes.materials.constitutive_laws import UserDefined +from structuralcodes.materials.basic import ( + ElasticMaterial, + ElasticPlasticMaterial, + GenericMaterial, +) +from structuralcodes.materials.constitutive_laws import Elastic, UserDefined @pytest.mark.parametrize( @@ -235,3 +239,57 @@ def test_userdefined_set_ultimate_strain_tuple(E, fy, eps_su): assert math.isclose( material.constitutive_law.get_ultimate_strain()[1], max(eps_su) ) + + +def test_name_elastic(): + """Test the name property of an elastic material.""" + # Create a material with a given name + given_name = 'A material' + material = ElasticMaterial(E=30000, density=2500, name=given_name) + + assert material.name == given_name + + # Create a material with a default name + counter = ElasticMaterial._material_counter + material = ElasticMaterial(E=30000, density=2500) + + assert material.name == f'ElasticMaterial_{counter}' + + +def test_name_elasticplastic(): + """Test the name property of an elastic plastic material.""" + # Create a material with a given name + given_name = 'A material' + material = ElasticPlasticMaterial( + E=210000, fy=355, density=7850, name=given_name + ) + + assert material.name == given_name + + # Create a material with a default name + counter = ElasticPlasticMaterial._material_counter + material = ElasticPlasticMaterial(E=210000, fy=355, density=7850) + + assert material.name == f'ElasticPlasticMaterial_{counter}' + + +def test_name_generic(): + """Test the name property of a generic material.""" + # Create a constitutive law + constitutive_law = Elastic(E=30000) + # Create a material with a given name + given_name = 'A material' + material = GenericMaterial( + constitutive_law=constitutive_law, density=2500, name=given_name + ) + + assert material.name == given_name + + # Create a material with a default name + counter = GenericMaterial._material_counter + material = GenericMaterial( + constitutive_law=constitutive_law, + density=2500, + ) + + assert material.name == f'GenericMaterial_{counter}' diff --git a/tests/test_sections/test_beam_section.py b/tests/test_sections/test_beam_section.py index 7e104329..e788c890 100644 --- a/tests/test_sections/test_beam_section.py +++ b/tests/test_sections/test_beam_section.py @@ -62,8 +62,9 @@ def test_rectangular_section(): assert geo.geometries[0].centroid[1] == 0 # Create the section (default Marin integrator) + counter = BeamSection._section_counter sec = BeamSection(geo) - assert sec.name == 'BeamSection' + assert sec.name == f'BeamSection_{counter}' assert math.isclose(sec.gross_properties.area, 200 * 400) @@ -170,8 +171,9 @@ def test_rectangular_section_tangent_stiffness(b, h, E, integrator): assert geo.polygon.centroid.coords[0][1] == 0 # Create the section with fiber integrator + counter = BeamSection._section_counter sec = BeamSection(geo, integrator=integrator, mesh_size=0.0001) - assert sec.name == 'BeamSection' + assert sec.name == f'BeamSection_{counter}' assert math.isclose(sec.gross_properties.area, b * h) @@ -435,8 +437,9 @@ def test_rectangular_section_tangent_stiffness_translated(b, h, E, integrator): assert geo.polygon.centroid.coords[0][1] == h / 2 # Create the section with fiber integrator + counter = BeamSection._section_counter sec = BeamSection(geo, integrator=integrator, mesh_size=0.0001) - assert sec.name == 'BeamSection' + assert sec.name == f'BeamSection_{counter}' assert math.isclose(sec.gross_properties.area, b * h) @@ -681,8 +684,9 @@ def test_holed_section(): assert geo.geometries[0].centroid[1] == 0 # Create the section (default Marin integrator) + counter = BeamSection._section_counter sec = BeamSection(geo) - assert sec.name == 'BeamSection' + assert sec.name == f'BeamSection_{counter}' assert math.isclose(sec.gross_properties.area, 260000) @@ -727,8 +731,9 @@ def test_u_section(): assert geo.geometries[0].centroid[1] == 0 # Create the section (default Marin integrator) + counter = BeamSection._section_counter sec = BeamSection(geo) - assert sec.name == 'BeamSection' + assert sec.name == f'BeamSection_{counter}' assert math.isclose(sec.gross_properties.area, 230000)