structuralcodes 0.2.0__py3-none-any.whl → 0.3.0__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.

@@ -6,9 +6,10 @@ import typing as t
6
6
 
7
7
  import numpy as np
8
8
  import triangle
9
- from numpy.typing import ArrayLike
9
+ from numpy.typing import ArrayLike, NDArray
10
10
  from shapely import Polygon
11
11
 
12
+ from structuralcodes.core.base import ConstitutiveLaw
12
13
  from structuralcodes.geometry import CompoundGeometry, SurfaceGeometry
13
14
 
14
15
  from ._section_integrator import SectionIntegrator
@@ -18,7 +19,7 @@ class FiberIntegrator(SectionIntegrator):
18
19
  """Section integrator based on the Marin algorithm."""
19
20
 
20
21
  def prepare_triangulation(self, geo: SurfaceGeometry) -> t.Dict:
21
- """Triangulate a SurfaceGeometry object.
22
+ """Prepare data for triangulating it with triangle.
22
23
 
23
24
  Arguments:
24
25
  geo (SurfaceGeometry): The geometry to triangulate.
@@ -60,26 +61,137 @@ class FiberIntegrator(SectionIntegrator):
60
61
  tri['holes'] = holes
61
62
  return tri
62
63
 
64
+ def triangulate(
65
+ self, geo: CompoundGeometry, mesh_size: float
66
+ ) -> t.List[t.Tuple[np.ndarray, np.ndarray, np.ndarray, ConstitutiveLaw]]:
67
+ """Triangulate the geometry discretizing it into fibers.
68
+
69
+ Arguments:
70
+ geo (CompoundGeometry): The geometry of the section.
71
+ mesh_size (float): Percentage of area (number from 0 to 1) max for
72
+ triangle elements.
73
+
74
+ Raises:
75
+ ValueError if mesh_size is <= 0.0 or if mesh_size is > 1.0.
76
+ """
77
+ # Check mesh_size being between 0 and 1.
78
+ if mesh_size <= 0 or mesh_size > 1:
79
+ raise ValueError('mesh_size is a number from 0 to 1')
80
+
81
+ triangulated_data = []
82
+
83
+ # For the surface geometries
84
+ for g in geo.geometries:
85
+ # prepare data structure for triangle module
86
+ tri = self.prepare_triangulation(g)
87
+ # define the maximum area of the triangles
88
+ max_area = g.area * mesh_size
89
+ # triangulate the geometry getting back the mesh
90
+ mesh = triangle.triangulate(tri, f'pq{30:.1f}Aa{max_area}o1')
91
+ mat = g.material
92
+ # Get x and y coordinates (centroid) and area for each fiber
93
+ x = []
94
+ y = []
95
+ area = []
96
+ for tr in mesh['triangles']:
97
+ # get centroid of triangle
98
+ xc = (
99
+ mesh['vertices'][tr[0]][0]
100
+ + mesh['vertices'][tr[1]][0]
101
+ + mesh['vertices'][tr[2]][0]
102
+ )
103
+ xc /= 3.0
104
+ x.append(xc)
105
+ yc = (
106
+ mesh['vertices'][tr[0]][1]
107
+ + mesh['vertices'][tr[1]][1]
108
+ + mesh['vertices'][tr[2]][1]
109
+ )
110
+ yc /= 3.0
111
+ y.append(yc)
112
+ # compute area
113
+ a = (
114
+ mesh['vertices'][tr[0]][0] * mesh['vertices'][tr[1]][1]
115
+ - mesh['vertices'][tr[0]][1] * mesh['vertices'][tr[1]][0]
116
+ )
117
+ a += (
118
+ mesh['vertices'][tr[1]][0] * mesh['vertices'][tr[2]][1]
119
+ - mesh['vertices'][tr[1]][1] * mesh['vertices'][tr[2]][0]
120
+ )
121
+ a += (
122
+ mesh['vertices'][tr[2]][0] * mesh['vertices'][tr[0]][1]
123
+ - mesh['vertices'][tr[2]][1] * mesh['vertices'][tr[0]][0]
124
+ )
125
+ a = abs(a) * 0.5
126
+ area.append(a)
127
+ # pointer to the material
128
+
129
+ # return back the triangulation data
130
+ triangulated_data.append(
131
+ (np.array(x), np.array(y), np.array(area), mat)
132
+ )
133
+ # For the reinforcement
134
+ # Tentative proposal for managing reinforcement (PointGeometry)
135
+ reinf_data = {}
136
+ # Preprocess geometries having the same material
137
+ for pg in geo.point_geometries:
138
+ x, y = pg._point.coords.xy
139
+ x = x[0]
140
+ y = y[0]
141
+ area = pg.area
142
+ mat = pg.material
143
+ if reinf_data.get(mat) is None:
144
+ reinf_data[mat] = [
145
+ np.array(x),
146
+ np.array(y),
147
+ np.array(area),
148
+ ]
149
+ else:
150
+ reinf_data[mat][0] = np.hstack((reinf_data[mat][0], x))
151
+ reinf_data[mat][1] = np.hstack((reinf_data[mat][1], y))
152
+ reinf_data[mat][2] = np.hstack((reinf_data[mat][2], area))
153
+ for mat, value in reinf_data.items():
154
+ triangulated_data.append((value[0], value[1], value[2], mat))
155
+
156
+ return triangulated_data
157
+
63
158
  def prepare_input(
64
- self, geo: CompoundGeometry, strain: ArrayLike, **kwargs
159
+ self,
160
+ geo: CompoundGeometry,
161
+ strain: ArrayLike,
162
+ integrate: t.Literal['stress', 'modulus'] = 'stress',
163
+ **kwargs,
65
164
  ) -> t.Tuple[t.Tuple[np.ndarray, np.ndarray, np.ndarray]]:
66
- """Prepare general input to the integration.
165
+ """Prepare general input to the integration of stress or material
166
+ modulus in the section.
67
167
 
68
- Calculate the stresses based on strains in a set of points.
168
+ Calculate the stress resultants or tangent section stiffness based on
169
+ strains in a set of points.
69
170
 
70
- Keyword Arguments:
171
+ Arguments:
71
172
  geo (CompoundGeometry): The geometry of the section.
72
173
  strain (ArrayLike): The strains and curvatures of the section,
73
174
  given in the format (ea, ky, kz) which are i) strain at 0,0,
74
175
  ii) curvature y axis, iii) curvature z axis.
75
- mesh_size: Percentage of area (number from 0 to 1) max for triangle
76
- elements.
176
+ integrate (str): a string indicating the quantity to integrate over
177
+ the section. It can be 'stress' or 'modulus'. When 'stress'
178
+ is selected, the return value will be the stress resultants N,
179
+ My, Mz, while if 'modulus' is selected, the return will be the
180
+ tangent section stiffness matrix (default is 'stress').
181
+
182
+ Keyword Arguments:
183
+ mesh_size (float): Percentage of area (number from 0 to 1) max for
184
+ triangle elements.
77
185
 
78
186
  Returns:
79
- Tuple(List, Dict): The prepared input representing a list with
187
+ Tuple(List): The prepared input representing a list with
80
188
  x-coordinates, y-coordinates and force for each fiber and a
81
- dictionary containing the triangulation data that can be stored and
189
+ list containing the triangulation data that can be stored and
82
190
  used later to avoid repetition of triangulation.
191
+
192
+ Raises:
193
+ ValueError: If a unkown value is passed to the `integrate`
194
+ parameter.
83
195
  """
84
196
  # This method should:
85
197
  # - discretize the section in a number of fibers (mesh_size)
@@ -92,102 +204,31 @@ class FiberIntegrator(SectionIntegrator):
92
204
  # No triangulation is provided, triangulate the section
93
205
  # Fiber integrator for generic section uses delaunay triangulation
94
206
  # for discretizing in fibers
95
- triangulated_data = []
207
+
96
208
  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(tri, f'pq{30:.1f}Aa{max_area}o1')
107
- mat = g.material
108
- # Get x and y coordinates (centroid) and area for each fiber
109
- x = []
110
- y = []
111
- area = []
112
- for tr in mesh['triangles']:
113
- # get centroid of triangle
114
- xc = (
115
- mesh['vertices'][tr[0]][0]
116
- + mesh['vertices'][tr[1]][0]
117
- + mesh['vertices'][tr[2]][0]
118
- )
119
- xc /= 3.0
120
- x.append(xc)
121
- yc = (
122
- mesh['vertices'][tr[0]][1]
123
- + mesh['vertices'][tr[1]][1]
124
- + mesh['vertices'][tr[2]][1]
125
- )
126
- yc /= 3.0
127
- y.append(yc)
128
- # compute area
129
- a = (
130
- mesh['vertices'][tr[0]][0] * mesh['vertices'][tr[1]][1]
131
- - mesh['vertices'][tr[0]][1]
132
- * mesh['vertices'][tr[1]][0]
133
- )
134
- a += (
135
- mesh['vertices'][tr[1]][0] * mesh['vertices'][tr[2]][1]
136
- - mesh['vertices'][tr[1]][1]
137
- * mesh['vertices'][tr[2]][0]
138
- )
139
- a += (
140
- mesh['vertices'][tr[2]][0] * mesh['vertices'][tr[0]][1]
141
- - mesh['vertices'][tr[2]][1]
142
- * mesh['vertices'][tr[0]][0]
143
- )
144
- a = abs(a) * 0.5
145
- area.append(a)
146
- # pointer to the material
147
-
148
- # return back the triangulation data
149
- triangulated_data.append(
150
- (np.array(x), np.array(y), np.array(area), mat)
151
- )
152
- # For the reinforcement
153
- # Tentative proposal for managing reinforcement (PointGeometry)
154
- reinf_data = {}
155
- # Preprocess geometries having the same material
156
- for pg in geo.point_geometries:
157
- x, y = pg._point.coords.xy
158
- x = x[0]
159
- y = y[0]
160
- area = pg.area
161
- mat = pg.material
162
- if reinf_data.get(mat) is None:
163
- reinf_data[mat] = [
164
- np.array(x),
165
- np.array(y),
166
- np.array(area),
167
- ]
168
- else:
169
- reinf_data[mat][0] = np.hstack((reinf_data[mat][0], x))
170
- reinf_data[mat][1] = np.hstack((reinf_data[mat][1], y))
171
- reinf_data[mat][2] = np.hstack((reinf_data[mat][2], area))
172
- for mat, value in reinf_data.items():
173
- triangulated_data.append((value[0], value[1], value[2], mat))
174
-
175
- x = []
209
+ triangulated_data = self.triangulate(geo, mesh_size)
210
+
176
211
  y = []
177
- F = []
212
+ z = []
213
+ IA = [] # integrand (stress or tangent) * area
178
214
  for tr in triangulated_data:
179
215
  # All have the same material
180
216
  strains = strain[0] - strain[2] * tr[0] + strain[1] * tr[1]
181
217
  # compute stresses in all materials
182
- stresses = tr[3].get_stress(strains)
183
- x.append(tr[0])
184
- y.append(tr[1])
185
- F.append(stresses * tr[2])
186
- prepared_input = [(np.hstack(x), np.hstack(y), np.hstack(F))]
218
+ if integrate == 'stress':
219
+ integrand = tr[3].get_stress(strains)
220
+ elif integrate == 'modulus':
221
+ integrand = tr[3].get_tangent(strains)
222
+ else:
223
+ raise ValueError(f'Unknown integrate type: {integrate}')
224
+ y.append(tr[0])
225
+ z.append(tr[1])
226
+ IA.append(integrand * tr[2])
227
+ prepared_input = [(np.hstack(y), np.hstack(z), np.hstack(IA))]
187
228
 
188
229
  return prepared_input, triangulated_data
189
230
 
190
- def integrate(
231
+ def integrate_stress(
191
232
  self,
192
233
  prepared_input: t.List[
193
234
  t.Tuple[int, np.ndarray, np.ndarray, np.ndarray]
@@ -202,35 +243,90 @@ class FiberIntegrator(SectionIntegrator):
202
243
  Tuple(float, float, float): The stress resultants N, Mx and My.
203
244
  """
204
245
  # Integration over all fibers
205
- x, y, F = prepared_input[0]
246
+ y, z, F = prepared_input[0]
206
247
 
207
248
  N = np.sum(F)
208
- Mx = np.sum(F * y)
209
- My = np.sum(-F * x)
249
+ My = np.sum(F * z)
250
+ Mz = np.sum(-F * y)
251
+
252
+ return N, My, Mz
253
+
254
+ def integrate_modulus(
255
+ self,
256
+ prepared_input: t.List[
257
+ t.Tuple[int, np.ndarray, np.ndarray, np.ndarray]
258
+ ],
259
+ ) -> NDArray:
260
+ """Integrate material modulus over the geometry to obtain section
261
+ stiffness.
262
+
263
+ Arguments:
264
+ prepared_input (List): The prepared input from .prepare_input().
265
+
266
+ Returns:
267
+ NDArray[float]: The stiffness matrix with shape (3,3).
268
+ """
269
+ # Integration over all fibers
270
+ y, z, MA = prepared_input[0]
210
271
 
211
- return N, Mx, My
272
+ stiffness = np.zeros((3, 3))
273
+ stiffness[0, 0] = np.sum(MA)
274
+ stiffness[0, 1] = stiffness[1, 0] = np.sum(MA * z)
275
+ stiffness[0, 2] = stiffness[2, 0] = np.sum(-MA * y)
276
+ stiffness[1, 1] = np.sum(z * z * MA)
277
+ stiffness[1, 2] = stiffness[2, 1] = np.sum(-y * z * MA)
278
+ stiffness[2, 2] = np.sum(y * y * MA)
279
+
280
+ return stiffness
212
281
 
213
282
  def integrate_strain_response_on_geometry(
214
- self, geo: CompoundGeometry, strain: ArrayLike, **kwargs
215
- ):
216
- """Integrate the strain response with the fiber algorithm.
283
+ self,
284
+ geo: CompoundGeometry,
285
+ strain: ArrayLike,
286
+ integrate: t.Literal['stress', 'modulus'] = 'stress',
287
+ **kwargs,
288
+ ) -> t.Tuple[t.Union[t.Tuple[float, float, float], np.ndarray], t.List]:
289
+ """Integrate stress or material modulus in the section with the fiber
290
+ algorithm.
217
291
 
218
292
  Arguments:
219
293
  geo (CompoundGeometry): The geometry of the section.
220
294
  strain (ArrayLike): The strains and curvatures of the section,
221
295
  given in the format (ea, ky, kz) which are i) strain at 0,0,
222
296
  ii) curvature y axis, iii) curvature z axis.
297
+ integrate (str): a string indicating the quantity to integrate over
298
+ the section. It can be 'stress' or 'modulus'. When 'stress'
299
+ is selected, the return value will be the stress resultants N,
300
+ My, Mz, while if 'modulus' is selected, the return will be the
301
+ section stiffness matrix (default is 'stress').
223
302
  mesh_size: Percentage of area (number from 0 to 1) max for triangle
224
303
  elements.
225
304
 
226
305
  Returns:
227
- Tuple(Tuple(float, float, float), Dict): The stress resultants N,
228
- Mx and My and the triangulation data.
306
+ Tuple(Union(Tuple(float, float, float), np.ndarray), List): The
307
+ first element is either a tuple of floats (for the stress
308
+ resultants (N, My, Mz) when `integrate='stress'`, or a numpy
309
+ array representing the stiffness matrix then `integrate='modulus'`.
310
+ The second element is a list with data from triangulation.
311
+
312
+ Example:
313
+ result, tri = integrate_strain_response_on_geometry(geo, strain,
314
+ integrate='tangent')
315
+ `result` will be the stiffness matrix (a 3x3 numpy array) if
316
+
317
+
318
+ Raises:
319
+ ValueError: If a unkown value is passed to the `integrate`
320
+ parameter.
229
321
  """
230
322
  # Prepare the general input based on the geometry and the input strains
231
323
  prepared_input, triangulated_data = self.prepare_input(
232
- geo, strain, **kwargs
324
+ geo, strain, integrate, **kwargs
233
325
  )
234
326
 
235
327
  # Return the calculated response
236
- return *self.integrate(prepared_input), triangulated_data
328
+ if integrate == 'stress':
329
+ return *self.integrate_stress(prepared_input), triangulated_data
330
+ if integrate == 'modulus':
331
+ return self.integrate_modulus(prepared_input), triangulated_data
332
+ raise ValueError(f'Unknown integrate type: {integrate}')