structuralcodes 0.5.0__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of structuralcodes might be problematic. Click here for more details.
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/PKG-INFO +2 -2
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/pyproject.toml +1 -1
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/__init__.py +1 -1
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/mc2010/_concrete_creep_and_shrinkage.py +2 -2
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/core/base.py +115 -4
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/geometry/__init__.py +2 -8
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/geometry/_geometry.py +11 -22
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/geometry/_reinforcement.py +1 -3
- structuralcodes-0.6.0/structuralcodes/geometry/profiles/__init__.py +19 -0
- structuralcodes-0.6.0/structuralcodes/geometry/profiles/_base_profile.py +305 -0
- structuralcodes-0.6.0/structuralcodes/geometry/profiles/_common_functions.py +194 -0
- structuralcodes-0.6.0/structuralcodes/geometry/profiles/_he.py +192 -0
- structuralcodes-0.6.0/structuralcodes/geometry/profiles/_ipe.py +130 -0
- structuralcodes-0.6.0/structuralcodes/geometry/profiles/_ipn.py +329 -0
- structuralcodes-0.6.0/structuralcodes/geometry/profiles/_ub.py +264 -0
- structuralcodes-0.6.0/structuralcodes/geometry/profiles/_ubp.py +227 -0
- structuralcodes-0.6.0/structuralcodes/geometry/profiles/_uc.py +276 -0
- structuralcodes-0.6.0/structuralcodes/geometry/profiles/_upn.py +315 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/basic/_elastic.py +18 -1
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/basic/_elasticplastic.py +18 -1
- structuralcodes-0.6.0/structuralcodes/materials/basic/_generic.py +43 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/concrete/__init__.py +3 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/concrete/_concrete.py +10 -1
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/concrete/_concreteEC2_2004.py +14 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/concrete/_concreteEC2_2023.py +14 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/concrete/_concreteMC2010.py +19 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/constitutive_laws/__init__.py +3 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/constitutive_laws/_elasticplastic.py +2 -2
- structuralcodes-0.6.0/structuralcodes/materials/constitutive_laws/_initial_strain.py +130 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/reinforcement/__init__.py +6 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/reinforcement/_reinforcement.py +10 -1
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py +14 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py +14 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/reinforcement/_reinforcementMC2010.py +14 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/sections/section_integrators/__init__.py +3 -1
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/sections/section_integrators/_marin_integrator.py +1 -1
- structuralcodes-0.5.0/structuralcodes/geometry/_steel_sections.py +0 -2155
- structuralcodes-0.5.0/structuralcodes/materials/basic/_generic.py +0 -26
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/LICENSE +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/README.md +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/__init__.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/ec2_2004/__init__.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/ec2_2004/_concrete_creep_and_shrinkage.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/ec2_2004/_concrete_material_properties.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/ec2_2004/_reinforcement_material_properties.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/ec2_2004/shear.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/ec2_2023/__init__.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/ec2_2023/_annexB_time_dependent.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/ec2_2023/_section5_materials.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/ec2_2023/_section9_sls.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/mc2010/__init__.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/mc2010/_concrete_interface_different_casting_times.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/mc2010/_concrete_material_properties.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/mc2010/_concrete_punching.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/mc2010/_concrete_shear.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/mc2010/_concrete_torsion.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/mc2010/_interface_concrete_steel_rebar.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/mc2010/_reinforcement_material_properties.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/codes/mc2020/__init__.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/core/__init__.py +0 -0
- {structuralcodes-0.5.0/structuralcodes/sections/section_integrators → structuralcodes-0.6.0/structuralcodes/core}/_marin_integration.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/core/_section_results.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/geometry/_circular.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/geometry/_rectangular.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/__init__.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/basic/__init__.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/constitutive_laws/_bilinearcompression.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/constitutive_laws/_elastic.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/constitutive_laws/_parabolarectangle.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/constitutive_laws/_popovics.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/constitutive_laws/_sargin.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/materials/constitutive_laws/_userdefined.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/sections/__init__.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/sections/_generic.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/sections/_rc_utils.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/sections/section_integrators/_factory.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/sections/section_integrators/_fiber_integrator.py +0 -0
- {structuralcodes-0.5.0 → structuralcodes-0.6.0}/structuralcodes/sections/section_integrators/_section_integrator.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structuralcodes
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: A Python package that contains models from structural design codes.
|
|
5
5
|
Author-email: fib - International Federation for Structural Concrete <info@fib-international.org>
|
|
6
|
-
Requires-Python: >=3.
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
@@ -10,7 +10,7 @@ authors = [
|
|
|
10
10
|
{name = "fib - International Federation for Structural Concrete", email = "info@fib-international.org"}
|
|
11
11
|
]
|
|
12
12
|
readme = "README.md"
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
16
|
"Operating System :: OS Independent"
|
|
@@ -95,13 +95,13 @@ def _check_initial_stress(sigma: float, fcm: float) -> None:
|
|
|
95
95
|
raise ValueError(
|
|
96
96
|
'The stress level exceeds the range of application.'
|
|
97
97
|
'Maximum allowable stress is 0.6*fcm. Current stress level '
|
|
98
|
-
f'is {round(abs(sigma)/fcm, 3)}*fcm.'
|
|
98
|
+
f'is {round(abs(sigma) / fcm, 3)}*fcm.'
|
|
99
99
|
)
|
|
100
100
|
if abs(sigma) > 0.4 * fcm:
|
|
101
101
|
warnings.warn(
|
|
102
102
|
'Initial stress is too high to consider the '
|
|
103
103
|
'concrete as an aging linear visco-elastic material: '
|
|
104
|
-
f'sigma = {round(abs(sigma)/fcm,3)}*fcm > 0.4*fcm. Nonlinear'
|
|
104
|
+
f'sigma = {round(abs(sigma) / fcm, 3)}*fcm > 0.4*fcm. Nonlinear'
|
|
105
105
|
' creep calculations are performed according to subclause '
|
|
106
106
|
'5.1.9.4.3 (d) of the fib Model Code 2010 to account for '
|
|
107
107
|
'large compressive stresses.'
|
|
@@ -15,17 +15,44 @@ class Material(abc.ABC):
|
|
|
15
15
|
"""Abstract base class for materials."""
|
|
16
16
|
|
|
17
17
|
_constitutive_law = None
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
_initial_strain: t.Optional[float] = None
|
|
19
|
+
_initial_stress: t.Optional[float] = None
|
|
20
|
+
_strain_compatibility: t.Optional[bool] = None
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
density: float,
|
|
25
|
+
initial_strain: t.Optional[float] = None,
|
|
26
|
+
initial_stress: t.Optional[float] = None,
|
|
27
|
+
strain_compatibility: t.Optional[bool] = None,
|
|
28
|
+
name: t.Optional[str] = None,
|
|
29
|
+
) -> None:
|
|
20
30
|
"""Initializes an instance of a new material.
|
|
21
31
|
|
|
22
32
|
Args:
|
|
23
|
-
density (float):
|
|
33
|
+
density (float): Density of the material in kg/m3.
|
|
24
34
|
|
|
25
35
|
Keyword Args:
|
|
36
|
+
initial_strain (Optional[float]): Initial strain of the material.
|
|
37
|
+
initial_stress (Optional[float]): Initial stress of the material.
|
|
38
|
+
strain_compatibility (Optional[bool]): Only relevant if
|
|
39
|
+
initial_strain or initial_stress are different from zero. If
|
|
40
|
+
True, the material deforms with the geometry. If False, the
|
|
41
|
+
stress in the material upon loading is kept constant
|
|
42
|
+
corresponding to the initial strain.
|
|
26
43
|
name (Optional[str]): descriptive name of the material
|
|
44
|
+
|
|
45
|
+
Raise:
|
|
46
|
+
ValueError: if both initial_strain and initial_stress are provided
|
|
27
47
|
"""
|
|
28
48
|
self._density = abs(density)
|
|
49
|
+
if initial_strain is not None and initial_stress is not None:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
'Both initial_strain and initial_stress cannot be provided.'
|
|
52
|
+
)
|
|
53
|
+
self._initial_strain = initial_strain
|
|
54
|
+
self._initial_stress = initial_stress
|
|
55
|
+
self._strain_compatibility = strain_compatibility
|
|
29
56
|
self._name = name if name is not None else 'Material'
|
|
30
57
|
|
|
31
58
|
@property
|
|
@@ -43,6 +70,88 @@ class Material(abc.ABC):
|
|
|
43
70
|
"""Returns the density of the material in kg/m3."""
|
|
44
71
|
return self._density
|
|
45
72
|
|
|
73
|
+
@property
|
|
74
|
+
def initial_strain(self):
|
|
75
|
+
"""Returns the initial strain of the material."""
|
|
76
|
+
return self._initial_strain
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def initial_stress(self):
|
|
80
|
+
"""Returns the initial stress of the material."""
|
|
81
|
+
return self._initial_stress
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def strain_compatibility(self):
|
|
85
|
+
"""Returns the strain compatibility of the material.
|
|
86
|
+
|
|
87
|
+
If true (default), the strain compatibility is enforced
|
|
88
|
+
haveing the same strain as in all other materials of the
|
|
89
|
+
section at the same point. If false, the strain compatibility
|
|
90
|
+
is not enforced and the initial strain is applied to the section
|
|
91
|
+
independently.
|
|
92
|
+
"""
|
|
93
|
+
return self._strain_compatibility
|
|
94
|
+
|
|
95
|
+
def _apply_initial_strain(self):
|
|
96
|
+
"""Wraps the current constitutive law to apply initial strain."""
|
|
97
|
+
strain_compatibility = (
|
|
98
|
+
self._strain_compatibility
|
|
99
|
+
if self._strain_compatibility is not None
|
|
100
|
+
else True
|
|
101
|
+
)
|
|
102
|
+
if self._initial_stress is not None:
|
|
103
|
+
# Specified a stress, compute the strain from it
|
|
104
|
+
self._initial_strain_from_stress()
|
|
105
|
+
if self._initial_strain is not None:
|
|
106
|
+
# Lazy import to avoid circular dependency
|
|
107
|
+
from structuralcodes.materials.constitutive_laws import ( # noqa: PLC0415
|
|
108
|
+
InitialStrain,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if self._initial_stress is None:
|
|
112
|
+
# Compute the stress from the strain
|
|
113
|
+
self._initial_stress = self._constitutive_law.get_stress(
|
|
114
|
+
self._initial_strain
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
self._constitutive_law = InitialStrain(
|
|
118
|
+
self._constitutive_law,
|
|
119
|
+
self._initial_strain,
|
|
120
|
+
strain_compatibility,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def _initial_strain_from_stress(self):
|
|
124
|
+
"""Computes the initial strain from the initial stress.
|
|
125
|
+
|
|
126
|
+
This function is called internally so it assumes that the
|
|
127
|
+
initial stress is not None
|
|
128
|
+
"""
|
|
129
|
+
# Iteratively compute the initial strain that gives the desired
|
|
130
|
+
# initial stress. Note that the wrapped law can be nonlinear
|
|
131
|
+
tol = 1e-12
|
|
132
|
+
max_iter = 100
|
|
133
|
+
target_stress = self._initial_stress
|
|
134
|
+
strain = 0.0
|
|
135
|
+
stress = self._constitutive_law.get_stress(strain)
|
|
136
|
+
d_stress = target_stress - stress
|
|
137
|
+
num_iter = 0
|
|
138
|
+
while abs(d_stress) > tol and num_iter < max_iter:
|
|
139
|
+
tangent = self._constitutive_law.get_tangent(strain)
|
|
140
|
+
if tangent == 0:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
'Tangent modulus = 0 during initial strain computation.'
|
|
143
|
+
)
|
|
144
|
+
d_strain = d_stress / tangent
|
|
145
|
+
strain += d_strain
|
|
146
|
+
stress = self._constitutive_law.get_stress(strain)
|
|
147
|
+
d_stress = target_stress - stress
|
|
148
|
+
num_iter += 1
|
|
149
|
+
|
|
150
|
+
if abs(d_stress) > tol:
|
|
151
|
+
raise RuntimeError('Failed to converge for given initial stress.')
|
|
152
|
+
|
|
153
|
+
self._initial_strain = strain
|
|
154
|
+
|
|
46
155
|
|
|
47
156
|
class ConstitutiveLaw(abc.ABC):
|
|
48
157
|
"""Abstract base class for constitutive laws."""
|
|
@@ -150,7 +259,9 @@ class ConstitutiveLaw(abc.ABC):
|
|
|
150
259
|
|
|
151
260
|
eps = np.concatenate((eps_neg, eps_pos))
|
|
152
261
|
sig = self.get_stress(eps)
|
|
153
|
-
from structuralcodes.materials.constitutive_laws import
|
|
262
|
+
from structuralcodes.materials.constitutive_laws import ( # noqa: PLC0415
|
|
263
|
+
UserDefined,
|
|
264
|
+
)
|
|
154
265
|
|
|
155
266
|
return UserDefined(eps, sig)
|
|
156
267
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Main entry point for geometry."""
|
|
2
2
|
|
|
3
|
+
from . import profiles
|
|
3
4
|
from ._circular import CircularGeometry
|
|
4
5
|
from ._geometry import (
|
|
5
6
|
CompoundGeometry,
|
|
@@ -14,7 +15,6 @@ from ._reinforcement import (
|
|
|
14
15
|
add_reinforcement_circle,
|
|
15
16
|
add_reinforcement_line,
|
|
16
17
|
)
|
|
17
|
-
from ._steel_sections import HE, IPE, IPN, UB, UBP, UC, UPN
|
|
18
18
|
|
|
19
19
|
__all__ = [
|
|
20
20
|
'Geometry',
|
|
@@ -22,13 +22,7 @@ __all__ = [
|
|
|
22
22
|
'SurfaceGeometry',
|
|
23
23
|
'CompoundGeometry',
|
|
24
24
|
'create_line_point_angle',
|
|
25
|
-
'
|
|
26
|
-
'HE',
|
|
27
|
-
'UB',
|
|
28
|
-
'UC',
|
|
29
|
-
'UBP',
|
|
30
|
-
'IPN',
|
|
31
|
-
'UPN',
|
|
25
|
+
'profiles',
|
|
32
26
|
'add_reinforcement',
|
|
33
27
|
'add_reinforcement_line',
|
|
34
28
|
'CircularGeometry',
|
|
@@ -79,6 +79,17 @@ class Geometry:
|
|
|
79
79
|
'This method should be implemented by subclasses'
|
|
80
80
|
)
|
|
81
81
|
|
|
82
|
+
def __add__(self, other: Geometry) -> CompoundGeometry:
|
|
83
|
+
"""Add operator "+" for geometries.
|
|
84
|
+
|
|
85
|
+
Arguments:
|
|
86
|
+
other (Geometry): The other geometry to add.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
CompoundGeometry: A new CompoundGeometry.
|
|
90
|
+
"""
|
|
91
|
+
return CompoundGeometry([self, other])
|
|
92
|
+
|
|
82
93
|
|
|
83
94
|
class PointGeometry(Geometry):
|
|
84
95
|
"""Class for a point geometry with material.
|
|
@@ -470,17 +481,6 @@ class SurfaceGeometry(Geometry):
|
|
|
470
481
|
# get the intersection
|
|
471
482
|
return self.polygon.intersection(lines_polygon)
|
|
472
483
|
|
|
473
|
-
def __add__(self, other: Geometry) -> CompoundGeometry:
|
|
474
|
-
"""Add operator "+" for geometries.
|
|
475
|
-
|
|
476
|
-
Arguments:
|
|
477
|
-
other (Geometry): The other geometry to add.
|
|
478
|
-
|
|
479
|
-
Returns:
|
|
480
|
-
CompoundGeometry: A new CompoundGeometry.
|
|
481
|
-
"""
|
|
482
|
-
return CompoundGeometry([self, other])
|
|
483
|
-
|
|
484
484
|
def __sub__(self, other: Geometry) -> SurfaceGeometry:
|
|
485
485
|
"""Add operator "-" for geometries.
|
|
486
486
|
|
|
@@ -789,17 +789,6 @@ class CompoundGeometry(Geometry):
|
|
|
789
789
|
processed_geoms.append(pg.rotate(angle, point, use_radians))
|
|
790
790
|
return CompoundGeometry(geometries=processed_geoms)
|
|
791
791
|
|
|
792
|
-
def __add__(self, other: Geometry) -> CompoundGeometry:
|
|
793
|
-
"""Add operator "+" for geometries.
|
|
794
|
-
|
|
795
|
-
Arguments:
|
|
796
|
-
other (Geometry): The other geometry to add.
|
|
797
|
-
|
|
798
|
-
Returns:
|
|
799
|
-
CompoundGeometry: A new CompoundGeometry.
|
|
800
|
-
"""
|
|
801
|
-
return CompoundGeometry([self, other])
|
|
802
|
-
|
|
803
792
|
def __sub__(self, other: Geometry) -> CompoundGeometry:
|
|
804
793
|
"""Add operator "-" for geometries.
|
|
805
794
|
|
|
@@ -78,8 +78,6 @@ def add_reinforcement_line(
|
|
|
78
78
|
CompoundGeometry: A compound geometry with the original geometry and
|
|
79
79
|
the reinforcement.
|
|
80
80
|
"""
|
|
81
|
-
from math import floor
|
|
82
|
-
|
|
83
81
|
p1 = np.array(coords_i)
|
|
84
82
|
p2 = np.array(coords_j)
|
|
85
83
|
distance = np.linalg.norm(p2 - p1)
|
|
@@ -100,7 +98,7 @@ def add_reinforcement_line(
|
|
|
100
98
|
elif s > 0:
|
|
101
99
|
# Provided the spacing
|
|
102
100
|
# 1. Compute the number of bars
|
|
103
|
-
n = floor(distance / s) + 1
|
|
101
|
+
n = math.floor(distance / s) + 1
|
|
104
102
|
# 2. Distribute the bars centered in the segment
|
|
105
103
|
d = (n - 1) * s
|
|
106
104
|
p1 = p1 + v * (distance - d) / 2.0
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Main entry point for profiles."""
|
|
2
|
+
|
|
3
|
+
from ._he import HE
|
|
4
|
+
from ._ipe import IPE
|
|
5
|
+
from ._ipn import IPN
|
|
6
|
+
from ._ub import UB
|
|
7
|
+
from ._ubp import UBP
|
|
8
|
+
from ._uc import UC
|
|
9
|
+
from ._upn import UPN
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
'HE',
|
|
13
|
+
'IPE',
|
|
14
|
+
'IPN',
|
|
15
|
+
'UB',
|
|
16
|
+
'UBP',
|
|
17
|
+
'UC',
|
|
18
|
+
'UPN',
|
|
19
|
+
]
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Base class for profiles."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from shapely import (
|
|
5
|
+
LinearRing,
|
|
6
|
+
LineString,
|
|
7
|
+
Polygon,
|
|
8
|
+
)
|
|
9
|
+
from shapely.affinity import rotate, translate
|
|
10
|
+
from shapely.ops import split
|
|
11
|
+
|
|
12
|
+
from structuralcodes.core._marin_integration import marin_integration
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseProfile:
|
|
16
|
+
"""Base class representing a profile.
|
|
17
|
+
|
|
18
|
+
Contains the common code for all profiles.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
"""Creates an empty base profile."""
|
|
23
|
+
self._polygon: Polygon = None
|
|
24
|
+
self._A: float = None
|
|
25
|
+
self._Iy: float = None
|
|
26
|
+
self._Iz: float = None
|
|
27
|
+
self._Icsi: float = None
|
|
28
|
+
self._Ieta: float = None
|
|
29
|
+
self._Iyz: float = None
|
|
30
|
+
self._Wely: float = None
|
|
31
|
+
self._Welz: float = None
|
|
32
|
+
self._iy: float = None
|
|
33
|
+
self._iz: float = None
|
|
34
|
+
self._Wply: float = None
|
|
35
|
+
self._Wplz: float = None
|
|
36
|
+
|
|
37
|
+
def _check_polygon_defined(self):
|
|
38
|
+
"""Just checks if polygon attribute is defined.
|
|
39
|
+
|
|
40
|
+
If the polygon is not defined (it should never happen), an exception
|
|
41
|
+
is Raised.
|
|
42
|
+
"""
|
|
43
|
+
# The polygon attribute should be already defined
|
|
44
|
+
if self._polygon is None:
|
|
45
|
+
raise RuntimeError(
|
|
46
|
+
'The polygon for some reason was not correctly defined.'
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def _find_plastic_neutral_axis_y(self) -> float:
|
|
50
|
+
"""Find posizion z of plastic neutral axes parallel to y.
|
|
51
|
+
|
|
52
|
+
We use bisection algorithm within the section limits.
|
|
53
|
+
"""
|
|
54
|
+
bounds = self.polygon.bounds
|
|
55
|
+
zmin, zmax = bounds[1], bounds[3]
|
|
56
|
+
|
|
57
|
+
zA = zmin
|
|
58
|
+
zB = zmax
|
|
59
|
+
|
|
60
|
+
daA = self._find_delta_area_above_minus_below(z=zA)
|
|
61
|
+
|
|
62
|
+
ITMAX = 200
|
|
63
|
+
it = 0
|
|
64
|
+
|
|
65
|
+
while (it < ITMAX) and (abs(zB - zA) > (zmax - zmin) * 1e-10):
|
|
66
|
+
zC = (zA + zB) / 2.0
|
|
67
|
+
daC = self._find_delta_area_above_minus_below(z=zC)
|
|
68
|
+
if abs(daC) < 1e-10:
|
|
69
|
+
break
|
|
70
|
+
if daA * daC < 0:
|
|
71
|
+
# The solution is between A and C
|
|
72
|
+
zB = zC
|
|
73
|
+
else:
|
|
74
|
+
# The solution is between C and B
|
|
75
|
+
zA = zC
|
|
76
|
+
daA = daC
|
|
77
|
+
it += 1
|
|
78
|
+
if it >= ITMAX:
|
|
79
|
+
s = f'Last iteration reached a unbalance of {daC}'
|
|
80
|
+
raise ValueError(f'Maximum number of iterations reached.\n{s}')
|
|
81
|
+
|
|
82
|
+
return zC
|
|
83
|
+
|
|
84
|
+
def _find_delta_area_above_minus_below(self, z: float) -> float:
|
|
85
|
+
"""Returns area difference between above and below parts.
|
|
86
|
+
|
|
87
|
+
Above and below parts are computed splitting the polygon with a line
|
|
88
|
+
parallel to Y axes at z coordinate.
|
|
89
|
+
"""
|
|
90
|
+
bounds = self._polygon.bounds
|
|
91
|
+
xmax = max(abs(bounds[0]), bounds[2])
|
|
92
|
+
line = LineString([[-xmax * 1.05, z], [xmax * 1.05, z]])
|
|
93
|
+
|
|
94
|
+
area_above = 0
|
|
95
|
+
area_below = 0
|
|
96
|
+
# divide polygons "above" and "below" line
|
|
97
|
+
if line.intersects(self._polygon):
|
|
98
|
+
result = split(self._polygon, line)
|
|
99
|
+
# divide polygons "above" and "below" line
|
|
100
|
+
for geom in result.geoms:
|
|
101
|
+
if LinearRing(
|
|
102
|
+
(line.coords[0], line.coords[1], geom.centroid.coords[0])
|
|
103
|
+
).is_ccw:
|
|
104
|
+
area_above += geom.area
|
|
105
|
+
else:
|
|
106
|
+
area_below += geom.area
|
|
107
|
+
else:
|
|
108
|
+
# not intersecting, all the polygon is above or below the line
|
|
109
|
+
geom = self.polygon
|
|
110
|
+
if LinearRing(
|
|
111
|
+
(line.coords[0], line.coords[1], geom.centroid.coords[0])
|
|
112
|
+
).is_ccw:
|
|
113
|
+
area_above += geom.area
|
|
114
|
+
else:
|
|
115
|
+
area_below += geom.area
|
|
116
|
+
return area_above - area_below
|
|
117
|
+
|
|
118
|
+
def _find_principals_direction_and_moments(self):
|
|
119
|
+
"""Computes principal direction and second area moments."""
|
|
120
|
+
eigres = np.linalg.eig(
|
|
121
|
+
np.array([[self.Iy, self.Iyz], [self.Iyz, self.Iz]])
|
|
122
|
+
)
|
|
123
|
+
max_idx = np.argmax(eigres[0])
|
|
124
|
+
min_idx = 0 if max_idx == 1 else 1
|
|
125
|
+
self._Icsi = eigres[0][max_idx]
|
|
126
|
+
self._Ieta = eigres[0][min_idx]
|
|
127
|
+
self._theta = np.arccos(
|
|
128
|
+
np.dot(np.array([1, 0]), eigres[1][:, max_idx])
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def A(self) -> float:
|
|
133
|
+
"""Returns area of profile."""
|
|
134
|
+
if self._A is None:
|
|
135
|
+
# Check if the polygon is defined
|
|
136
|
+
self._check_polygon_defined()
|
|
137
|
+
# Get the polygon coordinates:
|
|
138
|
+
xy = self._polygon.exterior.coords.xy
|
|
139
|
+
# Compute area
|
|
140
|
+
self._A = marin_integration(xy[0], xy[1], 0, 0)
|
|
141
|
+
return self._A
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def Iy(self) -> float:
|
|
145
|
+
"""Returns second moment of area around y axis."""
|
|
146
|
+
if self._Iy is None:
|
|
147
|
+
# Check if the polygon is defined
|
|
148
|
+
self._check_polygon_defined()
|
|
149
|
+
# Get the polygon coordinates:
|
|
150
|
+
xy = self._polygon.exterior.coords.xy
|
|
151
|
+
# Compute second moments of inertia
|
|
152
|
+
self._Iy = marin_integration(xy[0], xy[1], 0, 2)
|
|
153
|
+
return self._Iy
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def Iz(self) -> float:
|
|
157
|
+
"""Returns second moment of area around z axis."""
|
|
158
|
+
if self._Iz is None:
|
|
159
|
+
# Check if the polygon is defined
|
|
160
|
+
self._check_polygon_defined()
|
|
161
|
+
# Get the polygon coordinates:
|
|
162
|
+
xy = self._polygon.exterior.coords.xy
|
|
163
|
+
# Compute second moments of inertia
|
|
164
|
+
self._Iz = marin_integration(xy[0], xy[1], 2, 0)
|
|
165
|
+
return self._Iz
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def Iyz(self) -> float:
|
|
169
|
+
"""Returns product moment of inertia."""
|
|
170
|
+
if self._Iyz is None:
|
|
171
|
+
# Check if the polygon is defined
|
|
172
|
+
self._check_polygon_defined()
|
|
173
|
+
# Get the polygon coordinates:
|
|
174
|
+
xy = self._polygon.exterior.coords.xy
|
|
175
|
+
# Compute product moment of area
|
|
176
|
+
self._Iyz = marin_integration(xy[0], xy[1], 1, 1)
|
|
177
|
+
return self._Iyz
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def Icsi(self) -> float:
|
|
181
|
+
"""Returns second moment of area around principal csi axis.
|
|
182
|
+
|
|
183
|
+
It is assumed that Icsi is maximum second moment, while Ieta is the
|
|
184
|
+
minimum one.
|
|
185
|
+
"""
|
|
186
|
+
if self._Icsi is None:
|
|
187
|
+
self._find_principals_direction_and_moments()
|
|
188
|
+
return self._Icsi
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def Ieta(self) -> float:
|
|
192
|
+
"""Returns second moment of area around principal eta axis.
|
|
193
|
+
|
|
194
|
+
It is assumed that Icsi is maximum second moment, while Ieta is the
|
|
195
|
+
minimum one.
|
|
196
|
+
"""
|
|
197
|
+
if self._Ieta is None:
|
|
198
|
+
self._find_principals_direction_and_moments()
|
|
199
|
+
return self._Ieta
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def theta(self) -> float:
|
|
203
|
+
"""Returns angle between x and principal eta axis.
|
|
204
|
+
|
|
205
|
+
It is assumed that Icsi is maximum second moment, while Ieta is the
|
|
206
|
+
minimum one.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
float: The angle in radians.
|
|
210
|
+
"""
|
|
211
|
+
if self._theta is None:
|
|
212
|
+
self._find_principals_direction_and_moments()
|
|
213
|
+
return self._theta
|
|
214
|
+
|
|
215
|
+
def _compute_elastic_moduli(self):
|
|
216
|
+
"""Compute elastic moduli Wely and Welz."""
|
|
217
|
+
# Check if the polygon is defined
|
|
218
|
+
self._check_polygon_defined()
|
|
219
|
+
# For computing section modulus get bounds
|
|
220
|
+
bounds = self._polygon.bounds
|
|
221
|
+
xmax = max(abs(bounds[0]), bounds[2])
|
|
222
|
+
ymax = max(abs(bounds[1]), bounds[3])
|
|
223
|
+
# Then compute section modulus
|
|
224
|
+
self._Wely = self.Iy / ymax
|
|
225
|
+
self._Welz = self.Iz / xmax
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def Wely(self) -> float:
|
|
229
|
+
"""Returns section modulus in y direction."""
|
|
230
|
+
if self._Wely is None:
|
|
231
|
+
# Compute elastic moduli
|
|
232
|
+
self._compute_elastic_moduli()
|
|
233
|
+
return self._Wely
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def Welz(self) -> float:
|
|
237
|
+
"""Returns section modulus in z direction."""
|
|
238
|
+
if self._Welz is None:
|
|
239
|
+
# Compute elastic moduli
|
|
240
|
+
self._compute_elastic_moduli()
|
|
241
|
+
return self._Welz
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def Wply(self) -> float:
|
|
245
|
+
"""Returns plastic section modulus in y direction."""
|
|
246
|
+
if self._Wply is None:
|
|
247
|
+
# Check if the polygon is defined
|
|
248
|
+
self._check_polygon_defined()
|
|
249
|
+
# For computing section modulus get bounds
|
|
250
|
+
bounds = self._polygon.bounds
|
|
251
|
+
xmax = max(abs(bounds[0]), bounds[2])
|
|
252
|
+
# Compute plastic section modulus
|
|
253
|
+
# find plastic neutral axis parallel to y
|
|
254
|
+
self._Wply = 0
|
|
255
|
+
z_pna = self._find_plastic_neutral_axis_y()
|
|
256
|
+
poly = translate(self._polygon, xoff=0, yoff=-z_pna)
|
|
257
|
+
result = split(
|
|
258
|
+
poly,
|
|
259
|
+
LineString([[-xmax * 1.05, 0], [xmax * 1.05, 0]]),
|
|
260
|
+
)
|
|
261
|
+
for poly in result.geoms:
|
|
262
|
+
xy = poly.exterior.coords.xy
|
|
263
|
+
self._Wply += abs(marin_integration(xy[0], xy[1], 0, 1))
|
|
264
|
+
return self._Wply
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def Wplz(self) -> float:
|
|
268
|
+
"""Returns plastic section modulus in z direction."""
|
|
269
|
+
if self._Wplz is None:
|
|
270
|
+
# Check if the polygon is defined
|
|
271
|
+
self._check_polygon_defined()
|
|
272
|
+
# For computing section modulus get bounds
|
|
273
|
+
bounds = self._polygon.bounds
|
|
274
|
+
ymax = max(abs(bounds[1]), bounds[3])
|
|
275
|
+
# Compute plastic section modulus
|
|
276
|
+
# # find plastic neutral axis parallel to z
|
|
277
|
+
self._polygon = rotate(geom=self._polygon, angle=90, origin=(0, 0))
|
|
278
|
+
self._Wplz = 0
|
|
279
|
+
y_pna = self._find_plastic_neutral_axis_y()
|
|
280
|
+
poly = translate(self._polygon, xoff=0, yoff=-y_pna)
|
|
281
|
+
result = split(
|
|
282
|
+
poly,
|
|
283
|
+
LineString([[-ymax * 1.05, 0], [ymax * 1.05, 0]]),
|
|
284
|
+
)
|
|
285
|
+
for poly in result.geoms:
|
|
286
|
+
xy = poly.exterior.coords.xy
|
|
287
|
+
self._Wplz += abs(marin_integration(xy[0], xy[1], 0, 1))
|
|
288
|
+
self._polygon = rotate(
|
|
289
|
+
geom=self._polygon, angle=-90, origin=(0, 0)
|
|
290
|
+
)
|
|
291
|
+
return self._Wplz
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def iy(self) -> float:
|
|
295
|
+
"""Returns radius of inertia of profile."""
|
|
296
|
+
# Compute radius of inertia
|
|
297
|
+
self._iy = self._iy or (self.Iy / self.A) ** 0.5
|
|
298
|
+
return self._iy
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def iz(self) -> float:
|
|
302
|
+
"""Returns radius of inertia of profile."""
|
|
303
|
+
# Compute radius of inertia
|
|
304
|
+
self._iz = self._iz or (self.Iz / self.A) ** 0.5
|
|
305
|
+
return self._iz
|