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.

Files changed (50) hide show
  1. structuralcodes/__init__.py +17 -0
  2. structuralcodes/codes/__init__.py +79 -0
  3. structuralcodes/codes/ec2_2004/__init__.py +133 -0
  4. structuralcodes/codes/ec2_2004/_concrete_material_properties.py +239 -0
  5. structuralcodes/codes/ec2_2004/_reinforcement_material_properties.py +104 -0
  6. structuralcodes/codes/ec2_2004/_section_7_3_crack_control.py +941 -0
  7. structuralcodes/codes/ec2_2004/annex_b_shrink_and_creep.py +257 -0
  8. structuralcodes/codes/ec2_2004/shear.py +506 -0
  9. structuralcodes/codes/ec2_2023/__init__.py +104 -0
  10. structuralcodes/codes/ec2_2023/_annexB_time_dependent.py +17 -0
  11. structuralcodes/codes/ec2_2023/_section5_materials.py +1160 -0
  12. structuralcodes/codes/ec2_2023/_section9_sls.py +325 -0
  13. structuralcodes/codes/mc2010/__init__.py +169 -0
  14. structuralcodes/codes/mc2010/_concrete_creep_and_shrinkage.py +704 -0
  15. structuralcodes/codes/mc2010/_concrete_interface_different_casting_times.py +104 -0
  16. structuralcodes/codes/mc2010/_concrete_material_properties.py +463 -0
  17. structuralcodes/codes/mc2010/_concrete_punching.py +543 -0
  18. structuralcodes/codes/mc2010/_concrete_shear.py +749 -0
  19. structuralcodes/codes/mc2010/_concrete_torsion.py +164 -0
  20. structuralcodes/codes/mc2010/_reinforcement_material_properties.py +105 -0
  21. structuralcodes/core/__init__.py +1 -0
  22. structuralcodes/core/_section_results.py +211 -0
  23. structuralcodes/core/base.py +260 -0
  24. structuralcodes/geometry/__init__.py +25 -0
  25. structuralcodes/geometry/_geometry.py +875 -0
  26. structuralcodes/geometry/_steel_sections.py +2155 -0
  27. structuralcodes/materials/__init__.py +9 -0
  28. structuralcodes/materials/concrete/__init__.py +82 -0
  29. structuralcodes/materials/concrete/_concrete.py +114 -0
  30. structuralcodes/materials/concrete/_concreteEC2_2004.py +477 -0
  31. structuralcodes/materials/concrete/_concreteEC2_2023.py +435 -0
  32. structuralcodes/materials/concrete/_concreteMC2010.py +494 -0
  33. structuralcodes/materials/constitutive_laws.py +979 -0
  34. structuralcodes/materials/reinforcement/__init__.py +84 -0
  35. structuralcodes/materials/reinforcement/_reinforcement.py +172 -0
  36. structuralcodes/materials/reinforcement/_reinforcementEC2_2004.py +103 -0
  37. structuralcodes/materials/reinforcement/_reinforcementEC2_2023.py +93 -0
  38. structuralcodes/materials/reinforcement/_reinforcementMC2010.py +98 -0
  39. structuralcodes/sections/__init__.py +23 -0
  40. structuralcodes/sections/_generic.py +1249 -0
  41. structuralcodes/sections/_reinforcement.py +115 -0
  42. structuralcodes/sections/section_integrators/__init__.py +14 -0
  43. structuralcodes/sections/section_integrators/_factory.py +41 -0
  44. structuralcodes/sections/section_integrators/_fiber_integrator.py +238 -0
  45. structuralcodes/sections/section_integrators/_marin_integration.py +47 -0
  46. structuralcodes/sections/section_integrators/_marin_integrator.py +222 -0
  47. structuralcodes/sections/section_integrators/_section_integrator.py +49 -0
  48. structuralcodes-0.0.1.dist-info/METADATA +40 -0
  49. structuralcodes-0.0.1.dist-info/RECORD +50 -0
  50. 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