structuralcodes 0.1.1__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.

Files changed (30) hide show
  1. structuralcodes/__init__.py +1 -1
  2. structuralcodes/codes/ec2_2004/__init__.py +43 -11
  3. structuralcodes/codes/ec2_2004/_concrete_creep_and_shrinkage.py +529 -0
  4. structuralcodes/codes/mc2010/_concrete_creep_and_shrinkage.py +105 -73
  5. structuralcodes/core/_section_results.py +5 -19
  6. structuralcodes/core/base.py +42 -15
  7. structuralcodes/geometry/__init__.py +10 -1
  8. structuralcodes/geometry/_circular.py +81 -0
  9. structuralcodes/geometry/_geometry.py +4 -2
  10. structuralcodes/geometry/_rectangular.py +83 -0
  11. structuralcodes/geometry/_reinforcement.py +132 -5
  12. structuralcodes/materials/constitutive_laws/__init__.py +84 -0
  13. structuralcodes/materials/constitutive_laws/_bilinearcompression.py +183 -0
  14. structuralcodes/materials/constitutive_laws/_elastic.py +133 -0
  15. structuralcodes/materials/constitutive_laws/_elasticplastic.py +227 -0
  16. structuralcodes/materials/constitutive_laws/_parabolarectangle.py +255 -0
  17. structuralcodes/materials/constitutive_laws/_popovics.py +133 -0
  18. structuralcodes/materials/constitutive_laws/_sargin.py +115 -0
  19. structuralcodes/materials/constitutive_laws/_userdefined.py +262 -0
  20. structuralcodes/sections/__init__.py +2 -0
  21. structuralcodes/sections/_generic.py +174 -27
  22. structuralcodes/sections/_rc_utils.py +114 -0
  23. structuralcodes/sections/section_integrators/_fiber_integrator.py +204 -110
  24. structuralcodes/sections/section_integrators/_marin_integrator.py +273 -102
  25. structuralcodes/sections/section_integrators/_section_integrator.py +28 -4
  26. {structuralcodes-0.1.1.dist-info → structuralcodes-0.3.0.dist-info}/METADATA +2 -2
  27. {structuralcodes-0.1.1.dist-info → structuralcodes-0.3.0.dist-info}/RECORD +28 -18
  28. {structuralcodes-0.1.1.dist-info → structuralcodes-0.3.0.dist-info}/WHEEL +1 -1
  29. structuralcodes/codes/ec2_2004/annex_b_shrink_and_creep.py +0 -257
  30. structuralcodes/materials/constitutive_laws.py +0 -981
@@ -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,104 +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(
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 = []
209
+ triangulated_data = self.triangulate(geo, mesh_size)
210
+
178
211
  y = []
179
- F = []
212
+ z = []
213
+ IA = [] # integrand (stress or tangent) * area
180
214
  for tr in triangulated_data:
181
215
  # All have the same material
182
216
  strains = strain[0] - strain[2] * tr[0] + strain[1] * tr[1]
183
217
  # 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))]
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))]
189
228
 
190
229
  return prepared_input, triangulated_data
191
230
 
192
- def integrate(
231
+ def integrate_stress(
193
232
  self,
194
233
  prepared_input: t.List[
195
234
  t.Tuple[int, np.ndarray, np.ndarray, np.ndarray]
@@ -204,35 +243,90 @@ class FiberIntegrator(SectionIntegrator):
204
243
  Tuple(float, float, float): The stress resultants N, Mx and My.
205
244
  """
206
245
  # Integration over all fibers
207
- x, y, F = prepared_input[0]
246
+ y, z, F = prepared_input[0]
208
247
 
209
248
  N = np.sum(F)
210
- Mx = np.sum(F * y)
211
- 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]
212
271
 
213
- 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
214
281
 
215
282
  def integrate_strain_response_on_geometry(
216
- self, geo: CompoundGeometry, strain: ArrayLike, **kwargs
217
- ):
218
- """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.
219
291
 
220
292
  Arguments:
221
293
  geo (CompoundGeometry): The geometry of the section.
222
294
  strain (ArrayLike): The strains and curvatures of the section,
223
295
  given in the format (ea, ky, kz) which are i) strain at 0,0,
224
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').
225
302
  mesh_size: Percentage of area (number from 0 to 1) max for triangle
226
303
  elements.
227
304
 
228
305
  Returns:
229
- Tuple(Tuple(float, float, float), Dict): The stress resultants N,
230
- 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.
231
321
  """
232
322
  # Prepare the general input based on the geometry and the input strains
233
323
  prepared_input, triangulated_data = self.prepare_input(
234
- geo, strain, **kwargs
324
+ geo, strain, integrate, **kwargs
235
325
  )
236
326
 
237
327
  # Return the calculated response
238
- 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}')