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.

@@ -0,0 +1,83 @@
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 shapely import Polygon
12
+
13
+ from structuralcodes.core.base import ConstitutiveLaw, Material
14
+
15
+ from ._geometry import SurfaceGeometry
16
+
17
+
18
+ class RectangularGeometry(SurfaceGeometry):
19
+ """This is a wrapper class for defining a `SurfaceGeometry` of rectangular
20
+ shape with a homogeneous material.
21
+ """
22
+
23
+ _width: float
24
+ _height: float
25
+
26
+ def __init__(
27
+ self,
28
+ width: float,
29
+ height: float,
30
+ material: t.Union[Material, ConstitutiveLaw],
31
+ density: t.Optional[float] = None,
32
+ concrete: bool = False,
33
+ ) -> None:
34
+ """Initialize a RectangularGeometry.
35
+
36
+ Arguments:
37
+ width (float): The width of the geometry.
38
+ height (float): The height of the geometry.
39
+ material (Union(Material, ConstitutiveLaw)): A Material or
40
+ ConsitutiveLaw class applied to the geometry.
41
+ density (Optional(float)): When a ConstitutiveLaw is passed as
42
+ material, the density can be provided by this argument. When
43
+ material is a Material object the density is taken from the
44
+ material.
45
+ concrete (bool): Flag to indicate if the geometry is concrete. When
46
+ passing a Material as material, this is automatically inferred.
47
+
48
+ Note:
49
+ The RectangularGeometry is simply a wrapper for a SurfaceGeometry
50
+ object.
51
+ """
52
+ # Check that size is strictly positive
53
+ if width <= 0:
54
+ raise ValueError('Width must be a positive number.')
55
+ if height <= 0:
56
+ raise ValueError('Height must be a positive number.')
57
+
58
+ self._width = width
59
+ self._height = height
60
+
61
+ # Create the shapely polygon
62
+ polygon = Polygon(
63
+ (
64
+ (-width / 2, -height / 2),
65
+ (width / 2, -height / 2),
66
+ (width / 2, height / 2),
67
+ (-width / 2, height / 2),
68
+ )
69
+ )
70
+ # Pass everything to the base class
71
+ super().__init__(
72
+ poly=polygon, material=material, density=density, concrete=concrete
73
+ )
74
+
75
+ @property
76
+ def width(self):
77
+ """Returns the width of the rectangle."""
78
+ return self._width
79
+
80
+ @property
81
+ def height(self):
82
+ """Return the height of the rectangle."""
83
+ 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
@@ -103,7 +103,7 @@ class BilinearCompression(ConstitutiveLaw):
103
103
  # We are in tensile branch
104
104
  strains = None
105
105
  coeff.append((0.0,))
106
- elif strain[0] > self._eps_0:
106
+ elif strain[0] > self._eps_c:
107
107
  # We are in the linear branch
108
108
  strains = None
109
109
  a0 = self._E * strain[0]
@@ -129,6 +129,51 @@ class BilinearCompression(ConstitutiveLaw):
129
129
  coeff.append((self._fc,))
130
130
  return strains, coeff
131
131
 
132
+ def __marin_tangent__(
133
+ self, strain: t.Tuple[float, float]
134
+ ) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
135
+ """Returns coefficients and strain limits for Marin integration of
136
+ tangent in a simply formatted way.
137
+
138
+ Arguments:
139
+ strain (float, float): Tuple defining the strain profile: eps =
140
+ strain[0] + strain[1]*y.
141
+
142
+ Example:
143
+ [(0, -0.002), (-0.002, -0.003)]
144
+ [(a0, a1, a2), (a0)]
145
+ """
146
+ strains = []
147
+ coeff = []
148
+ if strain[1] == 0:
149
+ # Uniform strain equal to strain[0]
150
+ # understand in which branch we are
151
+ strain[0] = self.preprocess_strains_with_limits(strain[0])
152
+ if strain[0] > 0:
153
+ # We are in tensile branch
154
+ strains = None
155
+ coeff.append((0.0,))
156
+ elif strain[0] > self._eps_c:
157
+ # We are in the linear branch
158
+ strains = None
159
+ a0 = self._E
160
+ coeff.append((a0,))
161
+ else:
162
+ # We are in the constant branch or
163
+ # We are in a branch of non-resisting concrete
164
+ # Too much compression
165
+ strains = None
166
+ coeff.append((0.0,))
167
+ else:
168
+ # linear part
169
+ strains.append((self._eps_c, 0))
170
+ a0 = self._E
171
+ coeff.append((a0,))
172
+ # Constant part
173
+ strains.append((self._eps_cu, self._eps_c))
174
+ coeff.append((0.0,))
175
+ return strains, coeff
176
+
132
177
  def get_ultimate_strain(
133
178
  self, yielding: bool = False
134
179
  ) -> 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
  ]