structuralcodes 0.0.1__py3-none-any.whl
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/__init__.py +17 -0
- structuralcodes/codes/__init__.py +79 -0
- structuralcodes/codes/ec2_2004/__init__.py +133 -0
- structuralcodes/codes/ec2_2004/_concrete_material_properties.py +239 -0
- structuralcodes/codes/ec2_2004/_reinforcement_material_properties.py +104 -0
- structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py +941 -0
- structuralcodes/codes/ec2_2004/annex_b_shrink_and_creep.py +257 -0
- structuralcodes/codes/ec2_2004/shear.py +506 -0
- structuralcodes/codes/ec2_2023/__init__.py +104 -0
- structuralcodes/codes/ec2_2023/_annexB_time_dependent.py +17 -0
- structuralcodes/codes/ec2_2023/_section5_materials.py +1160 -0
- structuralcodes/codes/ec2_2023/_section9_sls.py +325 -0
- structuralcodes/codes/mc2010/__init__.py +169 -0
- structuralcodes/codes/mc2010/_concrete_creep_and_shrinkage.py +704 -0
- structuralcodes/codes/mc2010/_concrete_interface_different_casting_times.py +104 -0
- structuralcodes/codes/mc2010/_concrete_material_properties.py +463 -0
- structuralcodes/codes/mc2010/_concrete_punching.py +543 -0
- structuralcodes/codes/mc2010/_concrete_shear.py +749 -0
- structuralcodes/codes/mc2010/_concrete_torsion.py +164 -0
- structuralcodes/codes/mc2010/_reinforcement_material_properties.py +105 -0
- structuralcodes/core/__init__.py +1 -0
- structuralcodes/core/_section_results.py +211 -0
- structuralcodes/core/base.py +260 -0
- structuralcodes/geometry/__init__.py +25 -0
- structuralcodes/geometry/_geometry.py +875 -0
- structuralcodes/geometry/_steel_sections.py +2155 -0
- structuralcodes/materials/__init__.py +9 -0
- structuralcodes/materials/concrete/__init__.py +82 -0
- structuralcodes/materials/concrete/_concrete.py +114 -0
- structuralcodes/materials/concrete/_concreteEC2_2004.py +477 -0
- structuralcodes/materials/concrete/_concreteEC2_2023.py +435 -0
- structuralcodes/materials/concrete/_concreteMC2010.py +494 -0
- structuralcodes/materials/constitutive_laws.py +979 -0
- structuralcodes/materials/reinforcement/__init__.py +84 -0
- structuralcodes/materials/reinforcement/_reinforcement.py +172 -0
- structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py +103 -0
- structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py +93 -0
- structuralcodes/materials/reinforcement/_reinforcementMC2010.py +98 -0
- structuralcodes/sections/__init__.py +23 -0
- structuralcodes/sections/_generic.py +1249 -0
- structuralcodes/sections/_reinforcement.py +115 -0
- structuralcodes/sections/section_integrators/__init__.py +14 -0
- structuralcodes/sections/section_integrators/_factory.py +41 -0
- structuralcodes/sections/section_integrators/_fiber_integrator.py +238 -0
- structuralcodes/sections/section_integrators/_marin_integration.py +47 -0
- structuralcodes/sections/section_integrators/_marin_integrator.py +222 -0
- structuralcodes/sections/section_integrators/_section_integrator.py +49 -0
- structuralcodes-0.0.1.dist-info/METADATA +40 -0
- structuralcodes-0.0.1.dist-info/RECORD +50 -0
- structuralcodes-0.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Functions related to reinforcement definition."""
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from shapely import Point
|
|
7
|
+
|
|
8
|
+
from structuralcodes.core.base import ConstitutiveLaw, Material
|
|
9
|
+
from structuralcodes.geometry import (
|
|
10
|
+
CompoundGeometry,
|
|
11
|
+
PointGeometry,
|
|
12
|
+
SurfaceGeometry,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def add_reinforcement(
|
|
17
|
+
geo: t.Union[SurfaceGeometry, CompoundGeometry],
|
|
18
|
+
coords: t.Tuple[float, float],
|
|
19
|
+
diameter: float,
|
|
20
|
+
material: t.Union[Material, ConstitutiveLaw],
|
|
21
|
+
) -> CompoundGeometry:
|
|
22
|
+
"""Add a single bar given coordinate.
|
|
23
|
+
|
|
24
|
+
Arguments:
|
|
25
|
+
geo (Union(SurfaceGeometry, CompoundGeometry)): A geometry to which add
|
|
26
|
+
reinforcement.
|
|
27
|
+
coords (Tuple(float, float)): A tuple with cordinates of bar.
|
|
28
|
+
diameter (float): The diameter of the reinforcement.
|
|
29
|
+
material (Union(Material, ConstitutiveLaw)): A material or a
|
|
30
|
+
constitutive law for the behavior of the reinforcement.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
CompoundGeometry: A compound geometry with the original geometry and
|
|
34
|
+
the reinforcement.
|
|
35
|
+
"""
|
|
36
|
+
bar = PointGeometry(Point(coords), diameter, material)
|
|
37
|
+
return geo + bar
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def add_reinforcement_line(
|
|
41
|
+
geo: t.Union[SurfaceGeometry, CompoundGeometry],
|
|
42
|
+
coords_i: t.Tuple[float, float],
|
|
43
|
+
coords_j: t.Tuple[float, float],
|
|
44
|
+
diameter: float,
|
|
45
|
+
material: t.Union[Material, ConstitutiveLaw],
|
|
46
|
+
n: int = 0,
|
|
47
|
+
s: float = 0.0,
|
|
48
|
+
first: bool = True,
|
|
49
|
+
last: bool = True,
|
|
50
|
+
) -> CompoundGeometry:
|
|
51
|
+
"""Adds a set of bars distributed in a line.
|
|
52
|
+
|
|
53
|
+
Arguments:
|
|
54
|
+
geo (Union(SurfaceGeometry, CompoundGeometry)): The geometry used as
|
|
55
|
+
input.
|
|
56
|
+
coords_i (Tuple(float, float)): Coordinates of the initial point of
|
|
57
|
+
line.
|
|
58
|
+
coords_j (Tuple(float, float)): Coordinates of the final point of line.
|
|
59
|
+
diamter (float): The diameter of the bars.
|
|
60
|
+
material (Union(Material, ConstitutiveLaw)): A valid material or
|
|
61
|
+
constitutive law.
|
|
62
|
+
n (int): The number of bars to be distributed inside the line (default
|
|
63
|
+
= 0).
|
|
64
|
+
s (float): The distance between the bars (default = 0).
|
|
65
|
+
first (bool): Boolean indicating if placing the first bar (default =
|
|
66
|
+
True).
|
|
67
|
+
last (bool): Boolean indicating if placing the last bar (default =
|
|
68
|
+
True).
|
|
69
|
+
|
|
70
|
+
Note:
|
|
71
|
+
At least n or s should be greater than zero.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
CompoundGeometry: A compound geometry with the original geometry and
|
|
75
|
+
the reinforcement.
|
|
76
|
+
"""
|
|
77
|
+
from math import floor
|
|
78
|
+
|
|
79
|
+
p1 = np.array(coords_i)
|
|
80
|
+
p2 = np.array(coords_j)
|
|
81
|
+
distance = np.linalg.norm(p2 - p1)
|
|
82
|
+
v = (p2 - p1) / distance
|
|
83
|
+
if n > 0 and s > 0:
|
|
84
|
+
# Provided both the number of bars and spacing
|
|
85
|
+
# 1. Check the is enough space for fitting the bars
|
|
86
|
+
d = (n - 1) * s
|
|
87
|
+
if d > distance:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f'There is not room to fit {n} bars with a spacing of {s} \
|
|
90
|
+
in {distance}'
|
|
91
|
+
)
|
|
92
|
+
p1 = p1 + v * (distance - d) / 2.0
|
|
93
|
+
elif n > 0:
|
|
94
|
+
# Provided the number of bars
|
|
95
|
+
s = distance / (n - 1)
|
|
96
|
+
elif s > 0:
|
|
97
|
+
# Provided the spacing
|
|
98
|
+
# 1. Compute the number of bars
|
|
99
|
+
n = floor(distance / s) + 1
|
|
100
|
+
# 2. Distribute the bars centered in the segment
|
|
101
|
+
d = (n - 1) * s
|
|
102
|
+
p1 = p1 + v * (distance - d) / 2.0
|
|
103
|
+
else:
|
|
104
|
+
raise ValueError('At least n or s should be provided')
|
|
105
|
+
# add the bars
|
|
106
|
+
for i in range(n):
|
|
107
|
+
if i == 0 and not first:
|
|
108
|
+
continue
|
|
109
|
+
if i == n - 1 and not last:
|
|
110
|
+
continue
|
|
111
|
+
coords = p1 + v * s * i
|
|
112
|
+
geo = add_reinforcement(
|
|
113
|
+
geo, (coords[0], coords[1]), diameter, material
|
|
114
|
+
)
|
|
115
|
+
return geo
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Classes for integrating the response of sections."""
|
|
2
|
+
|
|
3
|
+
from ._factory import integrator_factory
|
|
4
|
+
from ._fiber_integrator import FiberIntegrator
|
|
5
|
+
from ._marin_integrator import MarinIntegrator, marin_integration
|
|
6
|
+
from ._section_integrator import SectionIntegrator
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'integrator_factory',
|
|
10
|
+
'FiberIntegrator',
|
|
11
|
+
'MarinIntegrator',
|
|
12
|
+
'SectionIntegrator',
|
|
13
|
+
'marin_integration',
|
|
14
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Factory for integrators."""
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from ._fiber_integrator import FiberIntegrator
|
|
6
|
+
from ._marin_integrator import MarinIntegrator
|
|
7
|
+
from ._section_integrator import SectionIntegrator
|
|
8
|
+
|
|
9
|
+
integrator_registry = {'marin': MarinIntegrator, 'fiber': FiberIntegrator}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class IntegratorFactory:
|
|
13
|
+
"""A factory for integrators."""
|
|
14
|
+
|
|
15
|
+
instances: t.Dict # A dict of integrators that have been created.
|
|
16
|
+
registry: t.Dict[str, SectionIntegrator]
|
|
17
|
+
|
|
18
|
+
def __init__(self, registry: t.Dict[str, SectionIntegrator]):
|
|
19
|
+
self.instances = {}
|
|
20
|
+
self.registry = registry
|
|
21
|
+
|
|
22
|
+
def __call__(
|
|
23
|
+
self, method: t.Literal['marin', 'fiber']
|
|
24
|
+
) -> SectionIntegrator:
|
|
25
|
+
"""Create an integrator based on its name.
|
|
26
|
+
|
|
27
|
+
Arguments:
|
|
28
|
+
method (str): The name of the integrator to use.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
SectionIntegrator: The section integrator.
|
|
32
|
+
"""
|
|
33
|
+
self.instances.setdefault(
|
|
34
|
+
method.lower(), self.registry.get(method.lower(), MarinIntegrator)
|
|
35
|
+
)
|
|
36
|
+
# Here we should throw a warning if the user input an integrator not
|
|
37
|
+
# given in the registry.
|
|
38
|
+
return self.instances.get(method.lower())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
integrator_factory = IntegratorFactory(integrator_registry)
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""The fiber section integrator."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations # To have clean hints of ArrayLike in docs
|
|
4
|
+
|
|
5
|
+
import typing as t
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import triangle
|
|
9
|
+
from numpy.typing import ArrayLike
|
|
10
|
+
from shapely import Polygon
|
|
11
|
+
|
|
12
|
+
from structuralcodes.geometry import CompoundGeometry, SurfaceGeometry
|
|
13
|
+
|
|
14
|
+
from ._section_integrator import SectionIntegrator
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FiberIntegrator(SectionIntegrator):
|
|
18
|
+
"""Section integrator based on the Marin algorithm."""
|
|
19
|
+
|
|
20
|
+
def prepare_triangulation(self, geo: SurfaceGeometry) -> t.Dict:
|
|
21
|
+
"""Triangulate a SurfaceGeometry object.
|
|
22
|
+
|
|
23
|
+
Arguments:
|
|
24
|
+
geo (SurfaceGeometry): The geometry to triangulate.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Dict: The triangulation data.
|
|
28
|
+
"""
|
|
29
|
+
# Create the tri dictionary
|
|
30
|
+
tri: dict[str:ArrayLike] = {}
|
|
31
|
+
# 1. External boundary process
|
|
32
|
+
# 1a. Get vertices, skipping the last one
|
|
33
|
+
vertices = np.column_stack(geo.polygon.exterior.xy)[:-1, :]
|
|
34
|
+
n_vertices = vertices.shape[0]
|
|
35
|
+
# 1b. Create segments
|
|
36
|
+
node_i = np.arange(n_vertices)
|
|
37
|
+
node_j = np.roll(node_i, -1)
|
|
38
|
+
segments = np.column_stack((node_i, node_j))
|
|
39
|
+
|
|
40
|
+
# 2. Process holes
|
|
41
|
+
holes = []
|
|
42
|
+
for interior in geo.polygon.interiors:
|
|
43
|
+
# 2a. Get vertices, skipping the last one
|
|
44
|
+
vertices_int = np.column_stack(interior.xy)[:-1, :]
|
|
45
|
+
n_vertices_int = vertices_int.shape[0]
|
|
46
|
+
# 2b. Create segments
|
|
47
|
+
node_i = np.arange(n_vertices_int) + n_vertices
|
|
48
|
+
node_j = np.roll(node_i, -1)
|
|
49
|
+
segments_int = np.column_stack((node_i, node_j))
|
|
50
|
+
c = Polygon(interior)
|
|
51
|
+
holes.append([c.centroid.x, c.centroid.y])
|
|
52
|
+
# Append to the global arrays
|
|
53
|
+
vertices = np.vstack((vertices, vertices_int))
|
|
54
|
+
segments = np.vstack((segments, segments_int))
|
|
55
|
+
n_vertices += n_vertices_int
|
|
56
|
+
# Return the dictionary with data for triangulate
|
|
57
|
+
tri['vertices'] = vertices
|
|
58
|
+
tri['segments'] = segments
|
|
59
|
+
if len(holes) > 0:
|
|
60
|
+
tri['holes'] = holes
|
|
61
|
+
return tri
|
|
62
|
+
|
|
63
|
+
def prepare_input(
|
|
64
|
+
self, geo: CompoundGeometry, strain: ArrayLike, **kwargs
|
|
65
|
+
) -> t.Tuple[t.Tuple[np.ndarray, np.ndarray, np.ndarray]]:
|
|
66
|
+
"""Prepare general input to the integration.
|
|
67
|
+
|
|
68
|
+
Calculate the stresses based on strains in a set of points.
|
|
69
|
+
|
|
70
|
+
Keyword Arguments:
|
|
71
|
+
geo (CompoundGeometry): The geometry of the section.
|
|
72
|
+
strain (ArrayLike): The strains and curvatures of the section,
|
|
73
|
+
given in the format (ea, ky, kz) which are i) strain at 0,0,
|
|
74
|
+
ii) curvature y axis, iii) curvature z axis.
|
|
75
|
+
mesh_size: Percentage of area (number from 0 to 1) max for triangle
|
|
76
|
+
elements.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Tuple(List, Dict): The prepared input representing a list with
|
|
80
|
+
x-coordinates, y-coordinates and force for each fiber and a
|
|
81
|
+
dictionary containing the triangulation data that can be stored and
|
|
82
|
+
used later to avoid repetition of triangulation.
|
|
83
|
+
"""
|
|
84
|
+
# This method should:
|
|
85
|
+
# - discretize the section in a number of fibers (mesh_size)
|
|
86
|
+
# - prepare input as y coordiates z coordinates forces
|
|
87
|
+
|
|
88
|
+
prepared_input = []
|
|
89
|
+
|
|
90
|
+
triangulated_data = kwargs.get('tri', None)
|
|
91
|
+
if triangulated_data is None:
|
|
92
|
+
# No triangulation is provided, triangulate the section
|
|
93
|
+
# Fiber integrator for generic section uses delaunay triangulation
|
|
94
|
+
# for discretizing in fibers
|
|
95
|
+
triangulated_data = []
|
|
96
|
+
mesh_size = kwargs.get('mesh_size', 0.01)
|
|
97
|
+
if mesh_size <= 0 or mesh_size > 1:
|
|
98
|
+
raise ValueError('mesh_size is a number from 0 to 1')
|
|
99
|
+
# For the surface geometries
|
|
100
|
+
for g in geo.geometries:
|
|
101
|
+
# prepare data structure for triangle module
|
|
102
|
+
tri = self.prepare_triangulation(g)
|
|
103
|
+
# define the maximum area of the triangles
|
|
104
|
+
max_area = g.area * mesh_size
|
|
105
|
+
# triangulate the geometry getting back the mesh
|
|
106
|
+
mesh = triangle.triangulate(
|
|
107
|
+
tri, f'pq{30:.1f}Aa{max_area:.1f}o1'
|
|
108
|
+
)
|
|
109
|
+
mat = g.material
|
|
110
|
+
# Get x and y coordinates (centroid) and area for each fiber
|
|
111
|
+
x = []
|
|
112
|
+
y = []
|
|
113
|
+
area = []
|
|
114
|
+
for tr in mesh['triangles']:
|
|
115
|
+
# get centroid of triangle
|
|
116
|
+
xc = (
|
|
117
|
+
mesh['vertices'][tr[0]][0]
|
|
118
|
+
+ mesh['vertices'][tr[1]][0]
|
|
119
|
+
+ mesh['vertices'][tr[2]][0]
|
|
120
|
+
)
|
|
121
|
+
xc /= 3.0
|
|
122
|
+
x.append(xc)
|
|
123
|
+
yc = (
|
|
124
|
+
mesh['vertices'][tr[0]][1]
|
|
125
|
+
+ mesh['vertices'][tr[1]][1]
|
|
126
|
+
+ mesh['vertices'][tr[2]][1]
|
|
127
|
+
)
|
|
128
|
+
yc /= 3.0
|
|
129
|
+
y.append(yc)
|
|
130
|
+
# compute area
|
|
131
|
+
a = (
|
|
132
|
+
mesh['vertices'][tr[0]][0] * mesh['vertices'][tr[1]][1]
|
|
133
|
+
- mesh['vertices'][tr[0]][1]
|
|
134
|
+
* mesh['vertices'][tr[1]][0]
|
|
135
|
+
)
|
|
136
|
+
a += (
|
|
137
|
+
mesh['vertices'][tr[1]][0] * mesh['vertices'][tr[2]][1]
|
|
138
|
+
- mesh['vertices'][tr[1]][1]
|
|
139
|
+
* mesh['vertices'][tr[2]][0]
|
|
140
|
+
)
|
|
141
|
+
a += (
|
|
142
|
+
mesh['vertices'][tr[2]][0] * mesh['vertices'][tr[0]][1]
|
|
143
|
+
- mesh['vertices'][tr[2]][1]
|
|
144
|
+
* mesh['vertices'][tr[0]][0]
|
|
145
|
+
)
|
|
146
|
+
a = abs(a) * 0.5
|
|
147
|
+
area.append(a)
|
|
148
|
+
# pointer to the material
|
|
149
|
+
|
|
150
|
+
# return back the triangulation data
|
|
151
|
+
triangulated_data.append(
|
|
152
|
+
(np.array(x), np.array(y), np.array(area), mat)
|
|
153
|
+
)
|
|
154
|
+
# For the reinforcement
|
|
155
|
+
# Tentative proposal for managing reinforcement (PointGeometry)
|
|
156
|
+
reinf_data = {}
|
|
157
|
+
# Preprocess geometries having the same material
|
|
158
|
+
for pg in geo.point_geometries:
|
|
159
|
+
x, y = pg._point.coords.xy
|
|
160
|
+
x = x[0]
|
|
161
|
+
y = y[0]
|
|
162
|
+
area = pg.area
|
|
163
|
+
mat = pg.material
|
|
164
|
+
if reinf_data.get(mat) is None:
|
|
165
|
+
reinf_data[mat] = [
|
|
166
|
+
np.array(x),
|
|
167
|
+
np.array(y),
|
|
168
|
+
np.array(area),
|
|
169
|
+
]
|
|
170
|
+
else:
|
|
171
|
+
reinf_data[mat][0] = np.hstack((reinf_data[mat][0], x))
|
|
172
|
+
reinf_data[mat][1] = np.hstack((reinf_data[mat][1], y))
|
|
173
|
+
reinf_data[mat][2] = np.hstack((reinf_data[mat][2], area))
|
|
174
|
+
for mat, value in reinf_data.items():
|
|
175
|
+
triangulated_data.append((value[0], value[1], value[2], mat))
|
|
176
|
+
|
|
177
|
+
x = []
|
|
178
|
+
y = []
|
|
179
|
+
F = []
|
|
180
|
+
for tr in triangulated_data:
|
|
181
|
+
# All have the same material
|
|
182
|
+
strains = strain[0] - strain[2] * tr[0] + strain[1] * tr[1]
|
|
183
|
+
# compute stresses in all materials
|
|
184
|
+
stresses = tr[3].get_stress(strains)
|
|
185
|
+
x.append(tr[0])
|
|
186
|
+
y.append(tr[1])
|
|
187
|
+
F.append(stresses * tr[2])
|
|
188
|
+
prepared_input = [(np.hstack(x), np.hstack(y), np.hstack(F))]
|
|
189
|
+
|
|
190
|
+
return prepared_input, triangulated_data
|
|
191
|
+
|
|
192
|
+
def integrate(
|
|
193
|
+
self,
|
|
194
|
+
prepared_input: t.List[
|
|
195
|
+
t.Tuple[int, np.ndarray, np.ndarray, np.ndarray]
|
|
196
|
+
],
|
|
197
|
+
) -> t.Tuple[float, float, float]:
|
|
198
|
+
"""Integrate stresses over the geometry.
|
|
199
|
+
|
|
200
|
+
Arguments:
|
|
201
|
+
prepared_input (List): The prepared input from .prepare_input().
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Tuple(float, float, float): The stress resultants N, Mx and My.
|
|
205
|
+
"""
|
|
206
|
+
# Integration over all fibers
|
|
207
|
+
x, y, F = prepared_input[0]
|
|
208
|
+
|
|
209
|
+
N = np.sum(F)
|
|
210
|
+
Mx = np.sum(F * y)
|
|
211
|
+
My = np.sum(-F * x)
|
|
212
|
+
|
|
213
|
+
return N, Mx, My
|
|
214
|
+
|
|
215
|
+
def integrate_strain_response_on_geometry(
|
|
216
|
+
self, geo: CompoundGeometry, strain: ArrayLike, **kwargs
|
|
217
|
+
):
|
|
218
|
+
"""Integrate the strain response with the fiber algorithm.
|
|
219
|
+
|
|
220
|
+
Arguments:
|
|
221
|
+
geo (CompoundGeometry): The geometry of the section.
|
|
222
|
+
strain (ArrayLike): The strains and curvatures of the section,
|
|
223
|
+
given in the format (ea, ky, kz) which are i) strain at 0,0,
|
|
224
|
+
ii) curvature y axis, iii) curvature z axis.
|
|
225
|
+
mesh_size: Percentage of area (number from 0 to 1) max for triangle
|
|
226
|
+
elements.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Tuple(Tuple(float, float, float), Dict): The stress resultants N,
|
|
230
|
+
Mx and My and the triangulation data.
|
|
231
|
+
"""
|
|
232
|
+
# Prepare the general input based on the geometry and the input strains
|
|
233
|
+
prepared_input, triangulated_data = self.prepare_input(
|
|
234
|
+
geo, strain, **kwargs
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Return the calculated response
|
|
238
|
+
return *self.integrate(prepared_input), triangulated_data
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Integration of area moments using algorithms by Joaquin Marin.
|
|
2
|
+
|
|
3
|
+
Marin, J.: Computing columns, footings and gates through moments of area.
|
|
4
|
+
Computers and Structures, 18(2), pp. 343-349, 1984.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import functools
|
|
8
|
+
import math
|
|
9
|
+
import typing as t
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@functools.lru_cache
|
|
13
|
+
def _coeff(m, n, j, k):
|
|
14
|
+
"""Calculate the binomial coefficient used in Marin integration."""
|
|
15
|
+
return math.comb(j + k, j) * math.comb(m + n - j - k, n - k)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def marin_integration(
|
|
19
|
+
y: t.List[float], z: t.List[float], m: int, n: int
|
|
20
|
+
) -> float:
|
|
21
|
+
"""Marin's algorithm for integrating a polynomial over a closed polygon.
|
|
22
|
+
|
|
23
|
+
The order of the polygon vertices is significant. If the points are in
|
|
24
|
+
counterclockwise order the integral is positive, otherwise the integral is
|
|
25
|
+
negative.
|
|
26
|
+
|
|
27
|
+
Arguments:
|
|
28
|
+
y (List(float)): Y coordinates of polygon vertices.
|
|
29
|
+
z (List(float)): Z coordinates of polygon vertices.
|
|
30
|
+
m (int): The degree of the polynomial in the y direction.
|
|
31
|
+
n (int): The degree of the polynomial in the z direction.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
float: The result of the integrated polynomial.
|
|
35
|
+
"""
|
|
36
|
+
mom = 0
|
|
37
|
+
for i in range(len(y) - 1):
|
|
38
|
+
# Sum over j
|
|
39
|
+
ssj = 0
|
|
40
|
+
for j in range(m + 1):
|
|
41
|
+
# Sum over k
|
|
42
|
+
ssk = 0
|
|
43
|
+
for k in range(n + 1):
|
|
44
|
+
ssk += _coeff(m, n, j, k) * z[i] ** (n - k) * z[i + 1] ** k
|
|
45
|
+
ssj += ssk * y[i] ** (m - j) * y[i + 1] ** j
|
|
46
|
+
mom += ssj * (y[i] * z[i + 1] - y[i + 1] * z[i])
|
|
47
|
+
return mom / (math.comb(m + n, n) * (m + n + 1) * (m + n + 2))
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""The Marin section integrator."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations # To have clean hints of ArrayLike in docs
|
|
4
|
+
|
|
5
|
+
import typing as t
|
|
6
|
+
from math import atan2, cos, sin
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from numpy.typing import ArrayLike
|
|
10
|
+
from shapely import MultiLineString, MultiPolygon, Polygon
|
|
11
|
+
from shapely.geometry.polygon import orient
|
|
12
|
+
|
|
13
|
+
from structuralcodes.geometry import CompoundGeometry, create_line_point_angle
|
|
14
|
+
|
|
15
|
+
from ._marin_integration import marin_integration
|
|
16
|
+
from ._section_integrator import SectionIntegrator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MarinIntegrator(SectionIntegrator):
|
|
20
|
+
"""Section integrator based on the Marin algorithm."""
|
|
21
|
+
|
|
22
|
+
def prepare_input(
|
|
23
|
+
self, geo: CompoundGeometry, strain: ArrayLike
|
|
24
|
+
) -> t.Tuple[float, t.Tuple[np.ndarray, np.ndarray, np.ndarray]]:
|
|
25
|
+
"""Prepare general input to the integration.
|
|
26
|
+
|
|
27
|
+
Calculate the stresses based on strains in a set of points.
|
|
28
|
+
|
|
29
|
+
Keyword Arguments:
|
|
30
|
+
geo (CompoundGeometry): The geometry of the section.
|
|
31
|
+
strain (ArrayLike): The strains and curvatures of the section,
|
|
32
|
+
given in the format (ea, ky, kz) which are i) strain at 0,0,
|
|
33
|
+
ii) curvature y axis, iii) curvature z axis.
|
|
34
|
+
mesh_size: Percentage of area (number from 0 to 1) max for triangle
|
|
35
|
+
elements.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Tuple(float, Tuple(ndarray, ndarray, ndarray)): The prepared input
|
|
39
|
+
represented as the angle of rotation computed (needed for rotating
|
|
40
|
+
back the resultants) and as a tuple with 3 ndarrys collecting
|
|
41
|
+
respectively y, z and stress coefficients for each sub-part.
|
|
42
|
+
"""
|
|
43
|
+
# This method should do the following tasks:
|
|
44
|
+
# - For each geo:
|
|
45
|
+
# - ask constitutive law strain limits and coefficients
|
|
46
|
+
# - For each material part, the part should furthermore be split
|
|
47
|
+
# according to constant, linear and parabolic stress distribution.
|
|
48
|
+
# - For each part collect coordinates y and z in separate np.ndarray
|
|
49
|
+
# iterables, and stress coefficients in a two-dimensional np.ndarray.
|
|
50
|
+
#
|
|
51
|
+
# The method should therefore return a tuple that collects the y, z,
|
|
52
|
+
# and stress coefficients for each part.
|
|
53
|
+
prepared_input = []
|
|
54
|
+
# 1. Rotate section in order to have neutral axis horizontal
|
|
55
|
+
angle = -atan2(strain[2], strain[1])
|
|
56
|
+
|
|
57
|
+
rotated_geom = geo.rotate(angle)
|
|
58
|
+
# 2. Get y coordinate of neutral axis in this new CRS
|
|
59
|
+
strain_rotated = [strain[0], (strain[2] ** 2 + strain[1] ** 2) ** 0.5]
|
|
60
|
+
|
|
61
|
+
# 3. For each SurfaceGeometry on the CompoundGeometry:
|
|
62
|
+
for g in rotated_geom.geometries:
|
|
63
|
+
# 3a. get coefficients and strain limits from constitutive law
|
|
64
|
+
if hasattr(g.material, '__marin__'):
|
|
65
|
+
strains, coeffs = g.material.__marin__(strain=strain_rotated)
|
|
66
|
+
else:
|
|
67
|
+
raise AttributeError(
|
|
68
|
+
f'The material object {g.material} of geometry {g} does \
|
|
69
|
+
not have implement the __marin__ function. \
|
|
70
|
+
Please implement the function or use another integrator, \
|
|
71
|
+
like '
|
|
72
|
+
'Fibre'
|
|
73
|
+
''
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# 3b. Subdivide the polygon at the different strain limits
|
|
77
|
+
def get_input_polygon(polygon, coeffs):
|
|
78
|
+
# Let's be sure to orient in the right way
|
|
79
|
+
if polygon.is_empty:
|
|
80
|
+
return
|
|
81
|
+
polygon = orient(polygon, 1)
|
|
82
|
+
if not polygon.exterior.is_ccw:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
'The exterior of a polygon should have vertices \
|
|
85
|
+
ordered ccw'
|
|
86
|
+
)
|
|
87
|
+
# Manage exterior part
|
|
88
|
+
x, y = polygon.exterior.coords.xy
|
|
89
|
+
x = np.array(x)
|
|
90
|
+
y = np.array(y)
|
|
91
|
+
prepared_input.append(
|
|
92
|
+
(0, np.array(x), np.array(y), np.array(coeffs))
|
|
93
|
+
)
|
|
94
|
+
# Manage holes
|
|
95
|
+
for i in polygon.interiors:
|
|
96
|
+
if i.is_ccw:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
'A inner hole should have cw coordinates'
|
|
99
|
+
)
|
|
100
|
+
x, y = i.coords.xy
|
|
101
|
+
prepared_input.append(
|
|
102
|
+
(0, np.array(x), np.array(y), np.array(coeffs))
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if strains is None:
|
|
106
|
+
get_input_polygon(g.polygon, coeffs[0])
|
|
107
|
+
else:
|
|
108
|
+
for p in range(len(strains)):
|
|
109
|
+
# Create the two lines for selecting the needed part
|
|
110
|
+
y0 = (
|
|
111
|
+
-(strain_rotated[0] - strains[p][0])
|
|
112
|
+
/ strain_rotated[1]
|
|
113
|
+
)
|
|
114
|
+
y1 = (
|
|
115
|
+
-(strain_rotated[0] - strains[p][1])
|
|
116
|
+
/ strain_rotated[1]
|
|
117
|
+
)
|
|
118
|
+
if y0 > y1:
|
|
119
|
+
y0, y1 = y1, y0
|
|
120
|
+
bbox = g.polygon.bounds
|
|
121
|
+
line0 = create_line_point_angle((0, y0), 0, bbox)
|
|
122
|
+
line1 = create_line_point_angle((0, y1), 0, bbox)
|
|
123
|
+
lines = MultiLineString((line0, line1))
|
|
124
|
+
# intersection
|
|
125
|
+
result = g.split_two_lines(lines=lines)
|
|
126
|
+
|
|
127
|
+
if isinstance(result, Polygon):
|
|
128
|
+
# If the result is a single polygon
|
|
129
|
+
get_input_polygon(result, coeffs[p])
|
|
130
|
+
elif isinstance(result, MultiPolygon):
|
|
131
|
+
# If the result is a MultiPolygon
|
|
132
|
+
for polygon in result.geoms:
|
|
133
|
+
get_input_polygon(polygon, coeffs[p])
|
|
134
|
+
# Tentative proposal for managing reinforcement (PointGeometry)
|
|
135
|
+
x = []
|
|
136
|
+
y = []
|
|
137
|
+
F = []
|
|
138
|
+
for pg in rotated_geom.point_geometries:
|
|
139
|
+
xp, yp = pg._point.coords.xy
|
|
140
|
+
xp = xp[0]
|
|
141
|
+
yp = yp[0]
|
|
142
|
+
A = pg.area
|
|
143
|
+
strain = strain_rotated[0] + strain_rotated[1] * yp
|
|
144
|
+
x.append(xp)
|
|
145
|
+
y.append(yp)
|
|
146
|
+
F.append(pg.material.get_stress(strain)[0] * A)
|
|
147
|
+
prepared_input.append((1, np.array(x), np.array(y), np.array(F)))
|
|
148
|
+
|
|
149
|
+
return angle, prepared_input
|
|
150
|
+
|
|
151
|
+
def integrate(
|
|
152
|
+
self,
|
|
153
|
+
angle: float,
|
|
154
|
+
prepared_input: t.List[
|
|
155
|
+
t.Tuple[int, np.ndarray, np.ndarray, np.ndarray]
|
|
156
|
+
],
|
|
157
|
+
) -> t.Tuple[float, float, float]:
|
|
158
|
+
"""Integrate stresses over the geometry.
|
|
159
|
+
|
|
160
|
+
Arguments:
|
|
161
|
+
prepared_input (List): The prepared input from .prepare_input().
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Tuple(float, float, float): The stress resultants N, Mx and My.
|
|
165
|
+
"""
|
|
166
|
+
# Set the stress resultants to zero
|
|
167
|
+
N, Mx, My = 0.0, 0.0, 0.0
|
|
168
|
+
|
|
169
|
+
# Loop through all parts of the section and add contributions
|
|
170
|
+
for i, y, z, stress_coeff in prepared_input:
|
|
171
|
+
if i == 0:
|
|
172
|
+
# Find integration order from shape of stress coeff array
|
|
173
|
+
n = stress_coeff.shape[0]
|
|
174
|
+
|
|
175
|
+
# Calculate area moments
|
|
176
|
+
area_moments_N = np.array(
|
|
177
|
+
[marin_integration(y, z, 0, k) for k in range(n)]
|
|
178
|
+
)
|
|
179
|
+
area_moments_Mx = np.array(
|
|
180
|
+
[marin_integration(y, z, 0, k + 1) for k in range(n)]
|
|
181
|
+
)
|
|
182
|
+
area_moments_My = np.array(
|
|
183
|
+
[marin_integration(y, z, 1, k) for k in range(n)]
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Calculate contributions to stress resultants
|
|
187
|
+
N += sum(stress_coeff * area_moments_N)
|
|
188
|
+
Mx += sum(stress_coeff * area_moments_Mx)
|
|
189
|
+
My += sum(stress_coeff * area_moments_My)
|
|
190
|
+
elif i == 1:
|
|
191
|
+
# Reinforcement
|
|
192
|
+
N += sum(stress_coeff)
|
|
193
|
+
Mx += sum(stress_coeff * z)
|
|
194
|
+
My += sum(stress_coeff * y)
|
|
195
|
+
|
|
196
|
+
# Rotate back to section CRS
|
|
197
|
+
T = np.array([[cos(-angle), -sin(-angle)], [sin(-angle), cos(-angle)]])
|
|
198
|
+
M = T @ np.array([[Mx], [-My]])
|
|
199
|
+
|
|
200
|
+
return N, M[0, 0], M[1, 0]
|
|
201
|
+
|
|
202
|
+
def integrate_strain_response_on_geometry(
|
|
203
|
+
self, geo: CompoundGeometry, strain: ArrayLike, **kwargs
|
|
204
|
+
):
|
|
205
|
+
"""Integrate the strain response with the Marin algorithm.
|
|
206
|
+
|
|
207
|
+
Arguments:
|
|
208
|
+
geo (CompoundGeometry): The geometry of the section.
|
|
209
|
+
strain (ArrayLike): The strains and curvatures of the section,
|
|
210
|
+
given in the format (ea, ky, kz) which are i) strain at 0,0,
|
|
211
|
+
ii) curvature y axis, iii) curvature z axis.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Tuple(Tuple(float, float, float), Dict): The stress resultants N,
|
|
215
|
+
Mx and My and the triangulation data.
|
|
216
|
+
"""
|
|
217
|
+
del kwargs
|
|
218
|
+
# Prepare the general input based on the geometry and the input strains
|
|
219
|
+
angle, prepared_input = self.prepare_input(geo, strain)
|
|
220
|
+
|
|
221
|
+
# Return the calculated response
|
|
222
|
+
return *self.integrate(angle, prepared_input), None
|