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.
- structuralcodes/__init__.py +1 -1
- structuralcodes/codes/ec2_2004/__init__.py +43 -11
- structuralcodes/codes/ec2_2004/_concrete_creep_and_shrinkage.py +529 -0
- structuralcodes/codes/mc2010/_concrete_creep_and_shrinkage.py +105 -73
- structuralcodes/core/_section_results.py +5 -19
- structuralcodes/core/base.py +42 -15
- structuralcodes/geometry/__init__.py +10 -1
- structuralcodes/geometry/_circular.py +81 -0
- structuralcodes/geometry/_geometry.py +4 -2
- structuralcodes/geometry/_rectangular.py +83 -0
- structuralcodes/geometry/_reinforcement.py +132 -5
- structuralcodes/materials/constitutive_laws/__init__.py +84 -0
- structuralcodes/materials/constitutive_laws/_bilinearcompression.py +183 -0
- structuralcodes/materials/constitutive_laws/_elastic.py +133 -0
- structuralcodes/materials/constitutive_laws/_elasticplastic.py +227 -0
- structuralcodes/materials/constitutive_laws/_parabolarectangle.py +255 -0
- structuralcodes/materials/constitutive_laws/_popovics.py +133 -0
- structuralcodes/materials/constitutive_laws/_sargin.py +115 -0
- structuralcodes/materials/constitutive_laws/_userdefined.py +262 -0
- structuralcodes/sections/__init__.py +2 -0
- structuralcodes/sections/_generic.py +174 -27
- structuralcodes/sections/_rc_utils.py +114 -0
- structuralcodes/sections/section_integrators/_fiber_integrator.py +204 -110
- structuralcodes/sections/section_integrators/_marin_integrator.py +273 -102
- structuralcodes/sections/section_integrators/_section_integrator.py +28 -4
- {structuralcodes-0.1.1.dist-info → structuralcodes-0.3.0.dist-info}/METADATA +2 -2
- {structuralcodes-0.1.1.dist-info → structuralcodes-0.3.0.dist-info}/RECORD +28 -18
- {structuralcodes-0.1.1.dist-info → structuralcodes-0.3.0.dist-info}/WHEEL +1 -1
- structuralcodes/codes/ec2_2004/annex_b_shrink_and_creep.py +0 -257
- structuralcodes/materials/constitutive_laws.py +0 -981
|
@@ -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
|
-
|
|
10
|
-
|
|
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
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Constitutive laws for materials."""
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from ...core.base import ConstitutiveLaw, Material
|
|
6
|
+
from ._bilinearcompression import BilinearCompression
|
|
7
|
+
from ._elastic import Elastic
|
|
8
|
+
from ._elasticplastic import ElasticPlastic
|
|
9
|
+
from ._parabolarectangle import ParabolaRectangle
|
|
10
|
+
from ._popovics import Popovics
|
|
11
|
+
from ._sargin import Sargin
|
|
12
|
+
from ._userdefined import UserDefined
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
'Elastic',
|
|
16
|
+
'ElasticPlastic',
|
|
17
|
+
'ParabolaRectangle',
|
|
18
|
+
'BilinearCompression',
|
|
19
|
+
'Popovics',
|
|
20
|
+
'Sargin',
|
|
21
|
+
'UserDefined',
|
|
22
|
+
'get_constitutive_laws_list',
|
|
23
|
+
'create_constitutive_law',
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
CONSTITUTIVE_LAWS: t.Dict[str, ConstitutiveLaw] = {
|
|
27
|
+
'elastic': Elastic,
|
|
28
|
+
'elasticplastic': ElasticPlastic,
|
|
29
|
+
'elasticperfectlyplastic': ElasticPlastic,
|
|
30
|
+
'bilinearcompression': BilinearCompression,
|
|
31
|
+
'parabolarectangle': ParabolaRectangle,
|
|
32
|
+
'popovics': Popovics,
|
|
33
|
+
'sargin': Sargin,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_constitutive_laws_list() -> t.List[str]:
|
|
38
|
+
"""Returns a list with valid keywords for constitutive law factory."""
|
|
39
|
+
return list(CONSTITUTIVE_LAWS.keys())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def create_constitutive_law(
|
|
43
|
+
constitutive_law_name: str, material: Material
|
|
44
|
+
) -> ConstitutiveLaw:
|
|
45
|
+
"""A factory function to create the constitutive law.
|
|
46
|
+
|
|
47
|
+
Arguments:
|
|
48
|
+
constitutive_law_name (str): A string defining a valid constitutive law
|
|
49
|
+
type. The available keys can be get with the method
|
|
50
|
+
`get_constitutive_laws_list`.
|
|
51
|
+
material (Material): The material containing the properties needed for
|
|
52
|
+
the definition of the constitutive law.
|
|
53
|
+
|
|
54
|
+
Note:
|
|
55
|
+
For working with this facotry function, the material class
|
|
56
|
+
implementations need to provide special dunder methods (__elastic__,
|
|
57
|
+
__parabolarectangle__, etc.) needed for the specific material that
|
|
58
|
+
return the kwargs needed to create the corresponding constitutive
|
|
59
|
+
law object. If the special dunder method is not found an exception
|
|
60
|
+
will be raised.
|
|
61
|
+
|
|
62
|
+
If the consitutive law selected is not available for the specific
|
|
63
|
+
material, an exception will be raised.
|
|
64
|
+
"""
|
|
65
|
+
law = None
|
|
66
|
+
const_law = CONSTITUTIVE_LAWS.get(constitutive_law_name.lower())
|
|
67
|
+
if const_law is not None:
|
|
68
|
+
method_name = f'__{constitutive_law_name}__'
|
|
69
|
+
# check if the material object has the special method needed
|
|
70
|
+
if hasattr(material, method_name):
|
|
71
|
+
method = getattr(material, method_name)
|
|
72
|
+
if callable(method):
|
|
73
|
+
# get the kwargs from the special dunder method
|
|
74
|
+
kwargs = method()
|
|
75
|
+
# create the constitutive law
|
|
76
|
+
law = const_law(**kwargs)
|
|
77
|
+
else:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f'Constitutive law {constitutive_law_name} not available for'
|
|
80
|
+
f' material {material.__class__.__name__}'
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
raise ValueError(f'Unknown constitutive law: {constitutive_law_name}')
|
|
84
|
+
return law
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Bilinear compression constitutive law."""
|
|
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
|
+
from numpy.typing import ArrayLike
|
|
9
|
+
|
|
10
|
+
from ...core.base import ConstitutiveLaw
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BilinearCompression(ConstitutiveLaw):
|
|
14
|
+
"""Class for Bilinear Elastic-PerfectlyPlastic Constitutive Law for
|
|
15
|
+
Concrete (only compression behavior).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
__materials__: t.Tuple[str] = ('concrete',)
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
fc: float,
|
|
23
|
+
eps_c: float,
|
|
24
|
+
eps_cu: t.Optional[float] = None,
|
|
25
|
+
name: t.Optional[str] = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Initialize a BilinearCompression Material.
|
|
28
|
+
|
|
29
|
+
Arguments:
|
|
30
|
+
fc (float): Compressive strength (negative number).
|
|
31
|
+
eps_c (float): Strain at compressive strength (pure number).
|
|
32
|
+
|
|
33
|
+
Keyword Arguments:
|
|
34
|
+
eps_cu (float): Ultimate strain (pure number).
|
|
35
|
+
name (str): A descriptive name for the constitutive law.
|
|
36
|
+
"""
|
|
37
|
+
name = name if name is not None else 'BilinearCompressionLaw'
|
|
38
|
+
super().__init__(name=name)
|
|
39
|
+
self._fc = -abs(fc)
|
|
40
|
+
self._eps_c = -abs(eps_c)
|
|
41
|
+
self._eps_cu = -abs(eps_cu)
|
|
42
|
+
self._E = self._fc / self._eps_c
|
|
43
|
+
|
|
44
|
+
def get_stress(
|
|
45
|
+
self, eps: t.Union[float, ArrayLike]
|
|
46
|
+
) -> t.Union[float, ArrayLike]:
|
|
47
|
+
"""Return the stress given strain."""
|
|
48
|
+
eps = eps if np.isscalar(eps) else np.atleast_1d(eps)
|
|
49
|
+
# Preprocess eps array in order
|
|
50
|
+
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
51
|
+
# Compute stress
|
|
52
|
+
# If it is a scalar
|
|
53
|
+
if np.isscalar(eps):
|
|
54
|
+
sig = 0
|
|
55
|
+
if self._fc / self._E <= eps <= 0:
|
|
56
|
+
sig = self._E * eps
|
|
57
|
+
return sig
|
|
58
|
+
# If it is an array
|
|
59
|
+
sig = self._E * eps
|
|
60
|
+
sig[sig < self._fc] = self._fc
|
|
61
|
+
sig[eps > 0] = 0
|
|
62
|
+
sig[eps < self._eps_cu] = 0
|
|
63
|
+
return sig
|
|
64
|
+
|
|
65
|
+
def get_tangent(
|
|
66
|
+
self, eps: t.Union[float, ArrayLike]
|
|
67
|
+
) -> t.Union[float, ArrayLike]:
|
|
68
|
+
"""Return the tangent for given strain."""
|
|
69
|
+
eps = eps if np.isscalar(eps) else np.atleast_1d(eps)
|
|
70
|
+
# If it is a scalar
|
|
71
|
+
if np.isscalar(eps):
|
|
72
|
+
tangent = 0
|
|
73
|
+
if self._fc / self._E <= eps <= 0:
|
|
74
|
+
tangent = self._E
|
|
75
|
+
return tangent
|
|
76
|
+
# If it is an array
|
|
77
|
+
tangent = np.ones_like(eps) * self._E
|
|
78
|
+
tangent[eps < self._eps_c] = 0.0
|
|
79
|
+
|
|
80
|
+
return tangent
|
|
81
|
+
|
|
82
|
+
def __marin__(
|
|
83
|
+
self, strain: t.Tuple[float, float]
|
|
84
|
+
) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
|
|
85
|
+
"""Returns coefficients and strain limits for Marin integration in a
|
|
86
|
+
simply formatted way.
|
|
87
|
+
|
|
88
|
+
Arguments:
|
|
89
|
+
strain (float, float): Tuple defining the strain profile: eps =
|
|
90
|
+
strain[0] + strain[1]*y.
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
[(0, -0.002), (-0.002, -0.003)]
|
|
94
|
+
[(a0, a1, a2), (a0)]
|
|
95
|
+
"""
|
|
96
|
+
strains = []
|
|
97
|
+
coeff = []
|
|
98
|
+
if strain[1] == 0:
|
|
99
|
+
# Uniform strain equal to strain[0]
|
|
100
|
+
# understand in which branch we are
|
|
101
|
+
strain[0] = self.preprocess_strains_with_limits(strain[0])
|
|
102
|
+
if strain[0] > 0:
|
|
103
|
+
# We are in tensile branch
|
|
104
|
+
strains = None
|
|
105
|
+
coeff.append((0.0,))
|
|
106
|
+
elif strain[0] > self._eps_c:
|
|
107
|
+
# We are in the linear branch
|
|
108
|
+
strains = None
|
|
109
|
+
a0 = self._E * strain[0]
|
|
110
|
+
a1 = self._E * strain[1]
|
|
111
|
+
coeff.append((a0, a1))
|
|
112
|
+
elif strain[0] >= self._eps_cu:
|
|
113
|
+
# We are in the constant branch
|
|
114
|
+
strains = None
|
|
115
|
+
coeff.append((self._fc,))
|
|
116
|
+
else:
|
|
117
|
+
# We are in a branch of non-resisting concrete
|
|
118
|
+
# Too much compression
|
|
119
|
+
strains = None
|
|
120
|
+
coeff.append((0.0,))
|
|
121
|
+
else:
|
|
122
|
+
# linear part
|
|
123
|
+
strains.append((self._eps_c, 0))
|
|
124
|
+
a0 = self._E * strain[0]
|
|
125
|
+
a1 = self._E * strain[1]
|
|
126
|
+
coeff.append((a0, a1))
|
|
127
|
+
# Constant part
|
|
128
|
+
strains.append((self._eps_cu, self._eps_c))
|
|
129
|
+
coeff.append((self._fc,))
|
|
130
|
+
return strains, coeff
|
|
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
|
+
|
|
177
|
+
def get_ultimate_strain(
|
|
178
|
+
self, yielding: bool = False
|
|
179
|
+
) -> t.Tuple[float, float]:
|
|
180
|
+
"""Return the ultimate strain (negative and positive)."""
|
|
181
|
+
if yielding:
|
|
182
|
+
return (self._eps_c, 100)
|
|
183
|
+
return (self._eps_cu, 100)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Elastic constitutive law."""
|
|
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
|
+
from numpy.typing import ArrayLike
|
|
9
|
+
|
|
10
|
+
from ...core.base import ConstitutiveLaw
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Elastic(ConstitutiveLaw):
|
|
14
|
+
"""Class for elastic constitutive law."""
|
|
15
|
+
|
|
16
|
+
__materials__: t.Tuple[str] = (
|
|
17
|
+
'concrete',
|
|
18
|
+
'steel',
|
|
19
|
+
'rebars',
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def __init__(self, E: float, name: t.Optional[str] = None) -> None:
|
|
23
|
+
"""Initialize an Elastic Material.
|
|
24
|
+
|
|
25
|
+
Arguments:
|
|
26
|
+
E (float): The elastic modulus.
|
|
27
|
+
|
|
28
|
+
Keyword Arguments:
|
|
29
|
+
name (str): A descriptive name for the constitutive law.
|
|
30
|
+
"""
|
|
31
|
+
name = name if name is not None else 'ElasticLaw'
|
|
32
|
+
super().__init__(name=name)
|
|
33
|
+
self._E = E
|
|
34
|
+
self._eps_su = None
|
|
35
|
+
|
|
36
|
+
def get_stress(
|
|
37
|
+
self, eps: t.Union[float, ArrayLike]
|
|
38
|
+
) -> t.Union[float, ArrayLike]:
|
|
39
|
+
"""Return stress given strain."""
|
|
40
|
+
eps = eps if np.isscalar(eps) else np.atleast_1d(eps)
|
|
41
|
+
return self._E * eps
|
|
42
|
+
|
|
43
|
+
def get_tangent(
|
|
44
|
+
self, eps: t.Union[float, ArrayLike]
|
|
45
|
+
) -> t.Union[float, ArrayLike]:
|
|
46
|
+
"""Return the tangent."""
|
|
47
|
+
if np.isscalar(eps):
|
|
48
|
+
return self._E
|
|
49
|
+
eps = np.atleast_1d(eps)
|
|
50
|
+
return np.ones_like(eps) * self._E
|
|
51
|
+
|
|
52
|
+
def __marin__(
|
|
53
|
+
self, strain: t.Tuple[float, float]
|
|
54
|
+
) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
|
|
55
|
+
"""Returns coefficients and strain limits for Marin integration in a
|
|
56
|
+
simply formatted way.
|
|
57
|
+
|
|
58
|
+
Arguments:
|
|
59
|
+
strain (float, float): Tuple defining the strain profile: eps =
|
|
60
|
+
strain[0] + strain[1]*y.
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
[(0, -0.002), (-0.002, -0.003)]
|
|
64
|
+
[(a0, a1, a2), (a0)]
|
|
65
|
+
"""
|
|
66
|
+
strains = None
|
|
67
|
+
a0 = self._E * strain[0]
|
|
68
|
+
a1 = self._E * strain[1]
|
|
69
|
+
coeff = [(a0, a1)]
|
|
70
|
+
return strains, coeff
|
|
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
|
+
|
|
91
|
+
def get_ultimate_strain(self, **kwargs) -> t.Tuple[float, float]:
|
|
92
|
+
"""Return the ultimate strain (negative and positive)."""
|
|
93
|
+
# There is no real strain limit, so set it to very large values
|
|
94
|
+
# unlesse specified by the user differently
|
|
95
|
+
del kwargs
|
|
96
|
+
return self._eps_su or (-100, 100)
|
|
97
|
+
|
|
98
|
+
def set_ultimate_strain(
|
|
99
|
+
self, eps_su=t.Union[float, t.Tuple[float, float]]
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Set ultimate strains for Elastic Material if needed.
|
|
102
|
+
|
|
103
|
+
Arguments:
|
|
104
|
+
eps_su (float or (float, float)): Defining ultimate strain. If a
|
|
105
|
+
single value is provided the same is adopted for both negative
|
|
106
|
+
and positive strains. If a tuple is provided, it should be
|
|
107
|
+
given as (negative, positive).
|
|
108
|
+
"""
|
|
109
|
+
if isinstance(eps_su, float):
|
|
110
|
+
self._eps_su = (-abs(eps_su), abs(eps_su))
|
|
111
|
+
elif isinstance(eps_su, tuple):
|
|
112
|
+
if len(eps_su) < 2:
|
|
113
|
+
raise ValueError(
|
|
114
|
+
'Two values need to be provided when setting the tuple'
|
|
115
|
+
)
|
|
116
|
+
eps_su_n = eps_su[0]
|
|
117
|
+
eps_su_p = eps_su[1]
|
|
118
|
+
if eps_su_p < eps_su_n:
|
|
119
|
+
eps_su_p, eps_su_n = eps_su_n, eps_su_p
|
|
120
|
+
if eps_su_p < 0:
|
|
121
|
+
raise ValueError(
|
|
122
|
+
'Positive ultimate strain should be non-negative'
|
|
123
|
+
)
|
|
124
|
+
if eps_su_n > 0:
|
|
125
|
+
raise ValueError(
|
|
126
|
+
'Negative utimate strain should be non-positive'
|
|
127
|
+
)
|
|
128
|
+
self._eps_su = (eps_su_n, eps_su_p)
|
|
129
|
+
else:
|
|
130
|
+
raise ValueError(
|
|
131
|
+
'set_ultimate_strain requires a single value or a tuple \
|
|
132
|
+
with two values'
|
|
133
|
+
)
|