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

@@ -0,0 +1,101 @@
1
+ """Classes for rectangular geometries.
2
+
3
+ The class `RectangularGeometry` represents a rectangular SurfaceGeometry with
4
+ homogenous material.
5
+ This class is simply a wrapper of `SurfaceGeometry` class and permits an easy
6
+ input by the user.
7
+ """
8
+
9
+ import typing as t
10
+
11
+ from numpy.typing import ArrayLike
12
+ from shapely import Polygon
13
+
14
+ from structuralcodes.core.base import ConstitutiveLaw, Material
15
+
16
+ from ._geometry import SurfaceGeometry
17
+
18
+
19
+ class RectangularGeometry(SurfaceGeometry):
20
+ """This is a wrapper class for defining a `SurfaceGeometry` of rectangular
21
+ shape with a homogeneous material.
22
+ """
23
+
24
+ _width: float
25
+ _height: float
26
+
27
+ def __init__(
28
+ self,
29
+ width: float,
30
+ height: float,
31
+ material: t.Union[Material, ConstitutiveLaw],
32
+ density: t.Optional[float] = None,
33
+ concrete: bool = False,
34
+ origin: t.Optional[ArrayLike] = None,
35
+ name: t.Optional[str] = None,
36
+ group_label: t.Optional[str] = None,
37
+ ) -> None:
38
+ """Initialize a RectangularGeometry.
39
+
40
+ Arguments:
41
+ width (float): The width of the geometry.
42
+ height (float): The height of the geometry.
43
+ material (Union(Material, ConstitutiveLaw)): A Material or
44
+ ConsitutiveLaw class applied to the geometry.
45
+ density (Optional(float)): When a ConstitutiveLaw is passed as
46
+ material, the density can be provided by this argument. When
47
+ material is a Material object the density is taken from the
48
+ material.
49
+ concrete (bool): Flag to indicate if the geometry is concrete. When
50
+ passing a Material as material, this is automatically inferred.
51
+ origin (Optional(ArrayLike)): The center point of the rectangle.
52
+ (0.0, 0.0) is used as default.
53
+ name (Optional(str)): The name to be given to the object.
54
+ group_label (Optional(str)): A label for grouping several objects.
55
+
56
+ Note:
57
+ The RectangularGeometry is simply a wrapper for a SurfaceGeometry
58
+ object.
59
+ """
60
+ # Check that size is strictly positive
61
+ if width <= 0:
62
+ raise ValueError('Width must be a positive number.')
63
+ if height <= 0:
64
+ raise ValueError('Height must be a positive number.')
65
+
66
+ self._width = width
67
+ self._height = height
68
+
69
+ # Parse origin
70
+ if origin is not None and len(origin) != 2:
71
+ raise ValueError('origin must be an ArrayLike with len == 2')
72
+ origin = origin if origin is not None else (0.0, 0.0)
73
+
74
+ # Create the shapely polygon
75
+ polygon = Polygon(
76
+ (
77
+ (-width / 2 + origin[0], -height / 2 + origin[1]),
78
+ (width / 2 + origin[0], -height / 2 + origin[1]),
79
+ (width / 2 + origin[0], height / 2 + origin[1]),
80
+ (-width / 2 + origin[0], height / 2 + origin[1]),
81
+ )
82
+ )
83
+ # Pass everything to the base class
84
+ super().__init__(
85
+ poly=polygon,
86
+ material=material,
87
+ density=density,
88
+ concrete=concrete,
89
+ name=name,
90
+ group_label=group_label,
91
+ )
92
+
93
+ @property
94
+ def width(self):
95
+ """Returns the width of the rectangle."""
96
+ return self._width
97
+
98
+ @property
99
+ def height(self):
100
+ """Return the height of the rectangle."""
101
+ return self._height
@@ -1,16 +1,14 @@
1
1
  """Functions related to reinforcement definition."""
2
2
 
3
+ import math
3
4
  import typing as t
4
5
 
5
6
  import numpy as np
6
7
  from shapely import Point
7
8
 
8
9
  from structuralcodes.core.base import ConstitutiveLaw, Material
9
- from structuralcodes.geometry import (
10
- CompoundGeometry,
11
- PointGeometry,
12
- SurfaceGeometry,
13
- )
10
+
11
+ from ._geometry import CompoundGeometry, PointGeometry, SurfaceGeometry
14
12
 
15
13
 
16
14
  def add_reinforcement(
@@ -125,3 +123,132 @@ def add_reinforcement_line(
125
123
  group_label=group_label,
126
124
  )
127
125
  return geo
126
+
127
+
128
+ def add_reinforcement_circle(
129
+ geo: t.Union[SurfaceGeometry, CompoundGeometry],
130
+ center: t.Tuple[float, float],
131
+ radius: float,
132
+ diameter: float,
133
+ material: t.Union[Material, ConstitutiveLaw],
134
+ n: int = 0,
135
+ s: float = 0.0,
136
+ first: bool = True,
137
+ last: bool = True,
138
+ start_angle: float = 0.0,
139
+ stop_angle: float = 2 * np.pi,
140
+ group_label: t.Optional[str] = None,
141
+ ) -> CompoundGeometry:
142
+ """Adds a set of bars distributed in a circular arch line.
143
+ By default the whole circle is considered. If one wants to specify a
144
+ circular arch, the `start_angle` and `stop_angle` attributes need to be
145
+ specified.
146
+
147
+ Arguments:
148
+ geo (Union(SurfaceGeometry, CompoundGeometry)): The geometry used as
149
+ input.
150
+ center (Tuple(float, float)): Coordinates of the center point of
151
+ the circle line where reinforcement will be added.
152
+ radius (float): Radius of the circle line where reinforcement will be
153
+ added.
154
+ diameter (float): The diameter of the bars.
155
+ material (Union(Material, ConstitutiveLaw)): A valid material or
156
+ constitutive law.
157
+ n (int): The number of bars to be distributed inside the line (default
158
+ = 0).
159
+ s (float): The distance between the bars (default = 0).
160
+ first (bool): Boolean indicating if placing the first bar (default =
161
+ True).
162
+ last (bool): Boolean indicating if placing the last bar (default =
163
+ True).
164
+ start_angle (float): Start angle (respect to X axis) for defining the
165
+ arch where to add bars in radians (default = 0)
166
+ stop_angle (float): Stop angle (respect to X axis) for defining the
167
+ arch where to add bars in radians (default = 2pi)
168
+ group_label (Optional(str)): A label for grouping several objects
169
+ (default is None).
170
+
171
+ Note:
172
+ At least n or s should be greater than zero.
173
+ Attribues start_angle and stop_angle by default are 0 and 2pi
174
+ respectively, so that bars will be distributed along the whole circle.
175
+ If only a portion of the circle must be used (i.e. an arch of
176
+ circumference), then set start and stop angles correspondingly.
177
+ stop_angle must always be larger than start_angle.
178
+
179
+ Returns:
180
+ CompoundGeometry: A compound geometry with the original geometry and
181
+ the reinforcement.
182
+ """
183
+ # Check that difference between stop and start angle is
184
+ # positive and less than 2pi
185
+ if stop_angle - start_angle <= 0 or stop_angle - start_angle > 2 * np.pi:
186
+ raise ValueError(
187
+ 'Stop angle should be larger than start angle and difference \
188
+ them should be at most 2pi.'
189
+ )
190
+ # Calculate length from start_angle to stop_angle
191
+ length = radius * (stop_angle - start_angle)
192
+
193
+ # If the whole circle, than deal with the case that would add an extra bar
194
+ whole = math.isclose(length - 2 * np.pi * radius, 0, abs_tol=1e-4)
195
+ add_n = 0 if not whole else 1
196
+
197
+ # delta_angle is used if we need to center the set of bars
198
+ # in the curve.
199
+ delta_angle = 0
200
+ if n > 0 and s > 0:
201
+ # Provided both the number of bars and spacing
202
+ # Check there is enough space for fitting the bars
203
+ n += add_n
204
+ needed_length = (n - 1) * s
205
+ if needed_length > length:
206
+ raise ValueError(
207
+ f'There is not room to fit {n} bars with a spacing of {s} \
208
+ in {length}'
209
+ )
210
+ # Compute delta_angle to make bars centered in the curvilinear segment
211
+ delta_angle = (length - needed_length) / 2.0 / radius
212
+ elif n > 0:
213
+ # Provided the number of bars
214
+ s = length / (n - 1)
215
+ # If we are distributing bars i the whole circle add a fictitious extra
216
+ # bar (than later will be removed).
217
+ n += add_n
218
+ elif s > 0:
219
+ # Provided the spacing
220
+ # 1. Compute the number of bars
221
+ n = math.floor(length / s) + 1
222
+ # 2. Distribute the bars centered in the curvilinear segment
223
+ needed_length = (n - 1) * s
224
+ delta_angle = (length - needed_length) / 2.0 / radius
225
+ # set whole to False bacause in this case we don't need to deal with
226
+ # the special case
227
+ whole = False
228
+ else:
229
+ raise ValueError('At least n or s should be provided')
230
+
231
+ phi_rebars = np.linspace(
232
+ start_angle + delta_angle, stop_angle - delta_angle, n
233
+ )
234
+ if whole:
235
+ n -= 1
236
+ phi_rebars = phi_rebars[:-1]
237
+
238
+ x = center[0] + radius * np.cos(phi_rebars)
239
+ y = center[1] + radius * np.sin(phi_rebars)
240
+
241
+ # add the bars
242
+ for i in range(n):
243
+ if i == 0 and not first:
244
+ continue
245
+ if i == n - 1 and not last:
246
+ continue
247
+ geo = add_reinforcement(
248
+ geo,
249
+ (x[i], y[i]),
250
+ diameter,
251
+ material,
252
+ group_label=group_label,
253
+ )
254
+ return geo
@@ -51,10 +51,10 @@ class BilinearCompression(ConstitutiveLaw):
51
51
  # Compute stress
52
52
  # If it is a scalar
53
53
  if np.isscalar(eps):
54
- sig = 0
55
- if self._fc / self._E <= eps <= 0:
56
- sig = self._E * eps
57
- return sig
54
+ if eps > 0 or eps < self._eps_cu:
55
+ return 0
56
+ return max(self._E * eps, self._fc)
57
+
58
58
  # If it is an array
59
59
  sig = self._E * eps
60
60
  sig[sig < self._fc] = self._fc
@@ -69,13 +69,14 @@ class BilinearCompression(ConstitutiveLaw):
69
69
  eps = eps if np.isscalar(eps) else np.atleast_1d(eps)
70
70
  # If it is a scalar
71
71
  if np.isscalar(eps):
72
- tangent = 0
73
- if self._fc / self._E <= eps <= 0:
74
- tangent = self._E
75
- return tangent
72
+ if self._eps_c < eps <= 0:
73
+ return self._E
74
+ return 0
75
+
76
76
  # If it is an array
77
77
  tangent = np.ones_like(eps) * self._E
78
- tangent[eps < self._eps_c] = 0.0
78
+ tangent[eps >= 0] = 0
79
+ tangent[eps < self._eps_c] = 0
79
80
 
80
81
  return tangent
81
82
 
@@ -103,7 +104,7 @@ class BilinearCompression(ConstitutiveLaw):
103
104
  # We are in tensile branch
104
105
  strains = None
105
106
  coeff.append((0.0,))
106
- elif strain[0] > self._eps_0:
107
+ elif strain[0] > self._eps_c:
107
108
  # We are in the linear branch
108
109
  strains = None
109
110
  a0 = self._E * strain[0]
@@ -129,6 +130,51 @@ class BilinearCompression(ConstitutiveLaw):
129
130
  coeff.append((self._fc,))
130
131
  return strains, coeff
131
132
 
133
+ def __marin_tangent__(
134
+ self, strain: t.Tuple[float, float]
135
+ ) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
136
+ """Returns coefficients and strain limits for Marin integration of
137
+ tangent in a simply formatted way.
138
+
139
+ Arguments:
140
+ strain (float, float): Tuple defining the strain profile: eps =
141
+ strain[0] + strain[1]*y.
142
+
143
+ Example:
144
+ [(0, -0.002), (-0.002, -0.003)]
145
+ [(a0, a1, a2), (a0)]
146
+ """
147
+ strains = []
148
+ coeff = []
149
+ if strain[1] == 0:
150
+ # Uniform strain equal to strain[0]
151
+ # understand in which branch we are
152
+ strain[0] = self.preprocess_strains_with_limits(strain[0])
153
+ if strain[0] > 0:
154
+ # We are in tensile branch
155
+ strains = None
156
+ coeff.append((0.0,))
157
+ elif strain[0] > self._eps_c:
158
+ # We are in the linear branch
159
+ strains = None
160
+ a0 = self._E
161
+ coeff.append((a0,))
162
+ else:
163
+ # We are in the constant branch or
164
+ # We are in a branch of non-resisting concrete
165
+ # Too much compression
166
+ strains = None
167
+ coeff.append((0.0,))
168
+ else:
169
+ # linear part
170
+ strains.append((self._eps_c, 0))
171
+ a0 = self._E
172
+ coeff.append((a0,))
173
+ # Constant part
174
+ strains.append((self._eps_cu, self._eps_c))
175
+ coeff.append((0.0,))
176
+ return strains, coeff
177
+
132
178
  def get_ultimate_strain(
133
179
  self, yielding: bool = False
134
180
  ) -> t.Tuple[float, float]:
@@ -69,6 +69,25 @@ class Elastic(ConstitutiveLaw):
69
69
  coeff = [(a0, a1)]
70
70
  return strains, coeff
71
71
 
72
+ def __marin_tangent__(
73
+ self, strain: t.Tuple[float, float]
74
+ ) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
75
+ """Returns coefficients and strain limits for Marin integration of
76
+ tangent in a simply formatted way.
77
+
78
+ Arguments:
79
+ strain (float, float): Tuple defining the strain profile: eps =
80
+ strain[0] + strain[1]*y.
81
+
82
+ Example:
83
+ [(0, -0.002), (-0.002, -0.003)]
84
+ [(a0, a1, a2), (a0)]
85
+ """
86
+ strains = None
87
+ a0 = self._E
88
+ coeff = [(a0,)]
89
+ return strains, coeff
90
+
72
91
  def get_ultimate_strain(self, **kwargs) -> t.Tuple[float, float]:
73
92
  """Return the ultimate strain (negative and positive)."""
74
93
  # There is no real strain limit, so set it to very large values
@@ -160,6 +160,61 @@ class ElasticPlastic(ConstitutiveLaw):
160
160
  coeff.append((a0, a1))
161
161
  return strains, coeff
162
162
 
163
+ def __marin_tangent__(
164
+ self, strain: t.Tuple[float, float]
165
+ ) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
166
+ """Returns coefficients and strain limits for Marin integration of
167
+ tangent in a simply formatted way.
168
+
169
+ Arguments:
170
+ strain (float, float): Tuple defining the strain profile: eps =
171
+ strain[0] + strain[1]*y.
172
+
173
+ Example:
174
+ [(0, -0.002), (-0.002, -0.003)]
175
+ [(a0, a1, a2), (a0)]
176
+ """
177
+ strains = []
178
+ coeff = []
179
+ eps_sy_n, eps_sy_p = self.get_ultimate_strain(yielding=True)
180
+ eps_su_n, eps_su_p = self.get_ultimate_strain()
181
+ if strain[1] == 0:
182
+ # Uniform strain equal to strain[0]
183
+ # Understand in which branch are we
184
+ strain[0] = self.preprocess_strains_with_limits(strain[0])
185
+ if strain[0] > eps_sy_p and strain[0] <= eps_su_p:
186
+ # We are in the Hardening part positive
187
+ strains = None
188
+ a0 = self._Eh
189
+ coeff.append((a0,))
190
+ elif strain[0] < eps_sy_n and strain[0] >= eps_su_n:
191
+ # We are in the Hardening part negative
192
+ strains = None
193
+ a0 = self._Eh
194
+ coeff.append((a0,))
195
+ elif abs(strain[0]) <= self._eps_sy:
196
+ # We are in the elastic part
197
+ strains = None
198
+ a0 = self._E
199
+ coeff.append((a0,))
200
+ else:
201
+ strains = None
202
+ coeff.append((0.0,))
203
+ else:
204
+ # Hardening part negative
205
+ strains.append((eps_su_n, eps_sy_n))
206
+ a0 = self._Eh
207
+ coeff.append((a0,))
208
+ # Elastic part
209
+ strains.append((eps_sy_n, eps_sy_p))
210
+ a0 = self._E
211
+ coeff.append((a0,))
212
+ # Hardening part positive
213
+ strains.append((eps_sy_p, eps_su_p))
214
+ a0 = self._Eh
215
+ coeff.append((a0,))
216
+ return strains, coeff
217
+
163
218
  def get_ultimate_strain(
164
219
  self, yielding: bool = False
165
220
  ) -> t.Tuple[float, float]:
@@ -189,6 +189,63 @@ class ParabolaRectangle(ConstitutiveLaw):
189
189
  coeff.append((self._fc,))
190
190
  return strains, coeff
191
191
 
192
+ def __marin_tangent__(
193
+ self, strain: t.Tuple[float, float]
194
+ ) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
195
+ """Returns coefficients and strain limits for Marin integration of
196
+ tangent in a simply formatted way.
197
+
198
+ Arguments:
199
+ strain (float, float): Tuple defining the strain profile: eps =
200
+ strain[0] + strain[1]*y.
201
+
202
+ Example:
203
+ [(0, -0.002), (-0.002, -0.003)]
204
+ [(a0, a1, a2), (a0)]
205
+ """
206
+ if self._n != 2:
207
+ # The constitutive law is not writtable as a polynomial,
208
+ # Call the generic distretizing method
209
+ return super().__marin_tangent__(strain=strain)
210
+
211
+ strains = []
212
+ coeff = []
213
+ if strain[1] == 0:
214
+ # Uniform strain equal to strain[0]
215
+ # understand in which branch are we
216
+ strain[0] = self.preprocess_strains_with_limits(strain[0])
217
+ if strain[0] > 0:
218
+ # We are in tensile branch
219
+ strains = None
220
+ coeff.append((0.0,))
221
+ elif strain[0] > self._eps_0:
222
+ # We are in the parabolic branch
223
+ strains = None
224
+ a0 = (
225
+ 2
226
+ * self._fc
227
+ / self._eps_0
228
+ * (1 - (strain[0] / self._eps_0))
229
+ )
230
+ a1 = -2 * self._fc / self._eps_0**2 * strain[1]
231
+ coeff.append((a0, a1))
232
+ else:
233
+ # We are in the constant branch or
234
+ # We are in a branch of non-resisting concrete
235
+ # Too much compression
236
+ strains = None
237
+ coeff.append((0.0,))
238
+ else:
239
+ # Parabolic part
240
+ strains.append((self._eps_0, 0))
241
+ a0 = 2 * self._fc / self._eps_0 * (1 - (strain[0] / self._eps_0))
242
+ a1 = -2 * self._fc / self._eps_0**2 * strain[1]
243
+ coeff.append((a0, a1))
244
+ # Constant part
245
+ strains.append((self._eps_u, self._eps_0))
246
+ coeff.append((0.0,))
247
+ return strains, coeff
248
+
192
249
  def get_ultimate_strain(
193
250
  self, yielding: bool = False
194
251
  ) -> t.Tuple[float, float]:
@@ -173,6 +173,50 @@ class UserDefined(ConstitutiveLaw):
173
173
 
174
174
  return strains, coeff
175
175
 
176
+ def __marin_tangent__(
177
+ self, strain: t.Tuple[float, float]
178
+ ) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
179
+ """Returns coefficients and strain limits for Marin integration of
180
+ tangent in a simply formatted way.
181
+
182
+ Arguments:
183
+ strain (float, float): Tuple defining the strain profile: eps =
184
+ strain[0] + strain[1]*y.
185
+
186
+ Example:
187
+ [(0, -0.002), (-0.002, -0.003)]
188
+ [(a0, a1, a2), (a0)]
189
+ """
190
+ strains = []
191
+ coeff = []
192
+ if strain[1] == 0:
193
+ # Uniform strain equal to strain[0]
194
+ # understand in which branch are we
195
+ strain[0] = self.preprocess_strains_with_limits(strain[0])
196
+ found = False
197
+ for i in range(len(self._x) - 1):
198
+ if self._x[i] <= strain[0] and self._x[i + 1] >= strain[0]:
199
+ strains = None
200
+ stiffness = (self._y[i + 1] - self._y[i]) / (
201
+ self._x[i + 1] - self._x[i]
202
+ )
203
+ coeff.append((stiffness,))
204
+ found = True
205
+ break
206
+ if not found:
207
+ strains = None
208
+ coeff.append((0.0,))
209
+ else:
210
+ for i in range(len(self._x) - 1):
211
+ # For each branch of the linear piecewise function
212
+ stiffness = (self._y[i + 1] - self._y[i]) / (
213
+ self._x[i + 1] - self._x[i]
214
+ )
215
+ strains.append((self._x[i], self._x[i + 1]))
216
+ coeff.append((stiffness,))
217
+
218
+ return strains, coeff
219
+
176
220
  def get_ultimate_strain(self, **kwargs) -> t.Tuple[float, float]:
177
221
  """Return the ultimate strain (negative and positive)."""
178
222
  del kwargs
@@ -1,6 +1,7 @@
1
1
  """Main entry point for sections."""
2
2
 
3
3
  from ._generic import GenericSection, GenericSectionCalculator
4
+ from ._rc_utils import calculate_elastic_cracked_properties
4
5
  from .section_integrators import (
5
6
  FiberIntegrator,
6
7
  MarinIntegrator,
@@ -17,4 +18,5 @@ __all__ = [
17
18
  'MarinIntegrator',
18
19
  'integrator_factory',
19
20
  'marin_integration',
21
+ 'calculate_elastic_cracked_properties',
20
22
  ]