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
|
@@ -1,981 +0,0 @@
|
|
|
1
|
-
"""Collection of some standard constitutive laws."""
|
|
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, Material
|
|
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(self, eps: ArrayLike) -> ArrayLike:
|
|
37
|
-
"""Return stress given strain."""
|
|
38
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
39
|
-
return self._E * eps
|
|
40
|
-
|
|
41
|
-
def get_tangent(self, eps: ArrayLike) -> ArrayLike:
|
|
42
|
-
"""Return the tangent."""
|
|
43
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
44
|
-
return np.ones_like(eps) * self._E
|
|
45
|
-
|
|
46
|
-
def __marin__(
|
|
47
|
-
self, strain: t.Tuple[float, float]
|
|
48
|
-
) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
|
|
49
|
-
"""Returns coefficients and strain limits for Marin integration in a
|
|
50
|
-
simply formatted way.
|
|
51
|
-
|
|
52
|
-
Arguments:
|
|
53
|
-
strain (float, float): Tuple defining the strain profile: eps =
|
|
54
|
-
strain[0] + strain[1]*y.
|
|
55
|
-
|
|
56
|
-
Example:
|
|
57
|
-
[(0, -0.002), (-0.002, -0.003)]
|
|
58
|
-
[(a0, a1, a2), (a0)]
|
|
59
|
-
"""
|
|
60
|
-
strains = None
|
|
61
|
-
a0 = self._E * strain[0]
|
|
62
|
-
a1 = self._E * strain[1]
|
|
63
|
-
coeff = [(a0, a1)]
|
|
64
|
-
return strains, coeff
|
|
65
|
-
|
|
66
|
-
def get_ultimate_strain(self, **kwargs) -> t.Tuple[float, float]:
|
|
67
|
-
"""Return the ultimate strain (negative and positive)."""
|
|
68
|
-
# There is no real strain limit, so set it to very large values
|
|
69
|
-
# unlesse specified by the user differently
|
|
70
|
-
del kwargs
|
|
71
|
-
return self._eps_su or (-100, 100)
|
|
72
|
-
|
|
73
|
-
def set_ultimate_strain(
|
|
74
|
-
self, eps_su=t.Union[float, t.Tuple[float, float]]
|
|
75
|
-
) -> None:
|
|
76
|
-
"""Set ultimate strains for Elastic Material if needed.
|
|
77
|
-
|
|
78
|
-
Arguments:
|
|
79
|
-
eps_su (float or (float, float)): Defining ultimate strain. If a
|
|
80
|
-
single value is provided the same is adopted for both negative
|
|
81
|
-
and positive strains. If a tuple is provided, it should be
|
|
82
|
-
given as (negative, positive).
|
|
83
|
-
"""
|
|
84
|
-
if isinstance(eps_su, float):
|
|
85
|
-
self._eps_su = (-abs(eps_su), abs(eps_su))
|
|
86
|
-
elif isinstance(eps_su, tuple):
|
|
87
|
-
if len(eps_su) < 2:
|
|
88
|
-
raise ValueError(
|
|
89
|
-
'Two values need to be provided when setting the tuple'
|
|
90
|
-
)
|
|
91
|
-
eps_su_n = eps_su[0]
|
|
92
|
-
eps_su_p = eps_su[1]
|
|
93
|
-
if eps_su_p < eps_su_n:
|
|
94
|
-
eps_su_p, eps_su_n = eps_su_n, eps_su_p
|
|
95
|
-
if eps_su_p < 0:
|
|
96
|
-
raise ValueError(
|
|
97
|
-
'Positive ultimate strain should be non-negative'
|
|
98
|
-
)
|
|
99
|
-
if eps_su_n > 0:
|
|
100
|
-
raise ValueError(
|
|
101
|
-
'Negative utimate strain should be non-positive'
|
|
102
|
-
)
|
|
103
|
-
self._eps_su = (eps_su_n, eps_su_p)
|
|
104
|
-
else:
|
|
105
|
-
raise ValueError(
|
|
106
|
-
'set_ultimate_strain requires a single value or a tuple \
|
|
107
|
-
with two values'
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class ElasticPlastic(ConstitutiveLaw):
|
|
112
|
-
"""Class for elastic-plastic Constitutive Law."""
|
|
113
|
-
|
|
114
|
-
__materials__: t.Tuple[str] = (
|
|
115
|
-
'steel',
|
|
116
|
-
'rebars',
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
def __init__(
|
|
120
|
-
self,
|
|
121
|
-
E: float,
|
|
122
|
-
fy: float,
|
|
123
|
-
Eh: float = 0.0,
|
|
124
|
-
eps_su: t.Optional[float] = None,
|
|
125
|
-
name: t.Optional[str] = None,
|
|
126
|
-
) -> None:
|
|
127
|
-
"""Initialize an Elastic-Plastic Material.
|
|
128
|
-
|
|
129
|
-
Arguments:
|
|
130
|
-
E (float): The elastic modulus.
|
|
131
|
-
fy (float): The yield strength.
|
|
132
|
-
|
|
133
|
-
Keyword Arguments:
|
|
134
|
-
Eh (float): The hardening modulus.
|
|
135
|
-
eps_su (float): The ultimate strain.
|
|
136
|
-
name (str): A descriptive name for the constitutive law.
|
|
137
|
-
"""
|
|
138
|
-
name = name if name is not None else 'ElasticPlasticLaw'
|
|
139
|
-
super().__init__(name=name)
|
|
140
|
-
if E > 0:
|
|
141
|
-
self._E = E
|
|
142
|
-
else:
|
|
143
|
-
raise ValueError('Elastic modulus E must be greater than zero')
|
|
144
|
-
self._fy = fy
|
|
145
|
-
self._Eh = Eh
|
|
146
|
-
self._eps_su = eps_su
|
|
147
|
-
self._eps_sy = fy / E
|
|
148
|
-
|
|
149
|
-
def get_stress(self, eps: ArrayLike) -> ArrayLike:
|
|
150
|
-
"""Return the stress given strain."""
|
|
151
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
152
|
-
# Preprocess eps array in order
|
|
153
|
-
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
154
|
-
# Compute stress
|
|
155
|
-
sig = self._E * eps
|
|
156
|
-
delta_sig = self._fy * (1 - self._Eh / self._E)
|
|
157
|
-
sig[sig < -self._fy] = eps[sig < -self._fy] * self._Eh - delta_sig
|
|
158
|
-
sig[sig > self._fy] = eps[sig > self._fy] * self._Eh + delta_sig
|
|
159
|
-
if self._eps_su is not None:
|
|
160
|
-
sig[eps > self._eps_su] = 0
|
|
161
|
-
sig[eps < -self._eps_su] = 0 # pylint: disable=E1130
|
|
162
|
-
return sig
|
|
163
|
-
|
|
164
|
-
def get_tangent(self, eps: ArrayLike) -> ArrayLike:
|
|
165
|
-
"""Return the tangent for given strain."""
|
|
166
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
167
|
-
tangent = np.ones_like(eps) * self._E
|
|
168
|
-
tangent[eps > self._eps_sy] = self._Eh
|
|
169
|
-
tangent[eps < -self._eps_sy] = self._Eh
|
|
170
|
-
|
|
171
|
-
return tangent
|
|
172
|
-
|
|
173
|
-
def __marin__(
|
|
174
|
-
self, strain: t.Tuple[float, float]
|
|
175
|
-
) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
|
|
176
|
-
"""Returns coefficients and strain limits for Marin integration in a
|
|
177
|
-
simply formatted way.
|
|
178
|
-
|
|
179
|
-
Arguments:
|
|
180
|
-
strain (float, float): Tuple defining the strain profile: eps =
|
|
181
|
-
strain[0] + strain[1]*y.
|
|
182
|
-
|
|
183
|
-
Example:
|
|
184
|
-
[(0, -0.002), (-0.002, -0.003)]
|
|
185
|
-
[(a0, a1, a2), (a0)]
|
|
186
|
-
"""
|
|
187
|
-
strains = []
|
|
188
|
-
coeff = []
|
|
189
|
-
eps_sy_n, eps_sy_p = self.get_ultimate_strain(yielding=True)
|
|
190
|
-
eps_su_n, eps_su_p = self.get_ultimate_strain()
|
|
191
|
-
if strain[1] == 0:
|
|
192
|
-
# Uniform strain equal to strain[0]
|
|
193
|
-
# Understand in which branch are we
|
|
194
|
-
strain[0] = self.preprocess_strains_with_limits(strain[0])[0]
|
|
195
|
-
if strain[0] > eps_sy_p and strain[0] <= eps_su_p:
|
|
196
|
-
# We are in the Hardening part positive
|
|
197
|
-
strains = None
|
|
198
|
-
a0 = self._Eh * strain[0] + self._fy * (1 - self._Eh / self._E)
|
|
199
|
-
a1 = self._Eh * strain[1]
|
|
200
|
-
coeff.append((a0, a1))
|
|
201
|
-
elif strain[0] < eps_sy_n and strain[0] >= eps_su_n:
|
|
202
|
-
# We are in the Hardening part negative
|
|
203
|
-
strains = None
|
|
204
|
-
a0 = self._Eh * strain[0] - self._fy * (1 - self._Eh / self._E)
|
|
205
|
-
a1 = self._Eh * strain[1]
|
|
206
|
-
coeff.append((a0, a1))
|
|
207
|
-
elif abs(strain[0]) <= self._eps_sy:
|
|
208
|
-
# We are in the elastic part
|
|
209
|
-
strains = None
|
|
210
|
-
a0 = self._E * strain[0]
|
|
211
|
-
a1 = self._E * strain[1]
|
|
212
|
-
coeff.append((a0, a1))
|
|
213
|
-
else:
|
|
214
|
-
strains = None
|
|
215
|
-
coeff.append((0.0,))
|
|
216
|
-
else:
|
|
217
|
-
# Hardening part negative
|
|
218
|
-
strains.append((eps_su_n, eps_sy_n))
|
|
219
|
-
a0 = self._Eh * strain[0] - self._fy * (1 - self._Eh / self._E)
|
|
220
|
-
a1 = self._Eh * strain[1]
|
|
221
|
-
coeff.append((a0, a1))
|
|
222
|
-
# Elastic part
|
|
223
|
-
strains.append((eps_sy_n, eps_sy_p))
|
|
224
|
-
a0 = self._E * strain[0]
|
|
225
|
-
a1 = self._E * strain[1]
|
|
226
|
-
coeff.append((a0, a1))
|
|
227
|
-
# Hardening part positive
|
|
228
|
-
strains.append((eps_sy_p, eps_su_p))
|
|
229
|
-
a0 = self._Eh * strain[0] + self._fy * (1 - self._Eh / self._E)
|
|
230
|
-
a1 = self._Eh * strain[1]
|
|
231
|
-
coeff.append((a0, a1))
|
|
232
|
-
return strains, coeff
|
|
233
|
-
|
|
234
|
-
def get_ultimate_strain(
|
|
235
|
-
self, yielding: bool = False
|
|
236
|
-
) -> t.Tuple[float, float]:
|
|
237
|
-
"""Return the ultimate strain (negative and positive)."""
|
|
238
|
-
if yielding:
|
|
239
|
-
return (-self._eps_sy, self._eps_sy)
|
|
240
|
-
# If not specified eps
|
|
241
|
-
if self._eps_su is None:
|
|
242
|
-
return (-self._eps_sy * 2, self._eps_sy * 2)
|
|
243
|
-
return (-self._eps_su, self._eps_su)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
class ParabolaRectangle(ConstitutiveLaw):
|
|
247
|
-
"""Class for parabola rectangle constitutive law.
|
|
248
|
-
|
|
249
|
-
The stresses and strains are assumed negative in compression and positive
|
|
250
|
-
in tension.
|
|
251
|
-
"""
|
|
252
|
-
|
|
253
|
-
__materials__: t.Tuple[str] = ('concrete',)
|
|
254
|
-
|
|
255
|
-
def __init__(
|
|
256
|
-
self,
|
|
257
|
-
fc: float,
|
|
258
|
-
eps_0: float = -0.002,
|
|
259
|
-
eps_u: float = -0.0035,
|
|
260
|
-
n: float = 2.0,
|
|
261
|
-
name: t.Optional[str] = None,
|
|
262
|
-
) -> None:
|
|
263
|
-
"""Initialize a Parabola-Rectangle Material.
|
|
264
|
-
|
|
265
|
-
Arguments:
|
|
266
|
-
fc (float): The strength of concrete in compression.
|
|
267
|
-
|
|
268
|
-
Keyword Arguments:
|
|
269
|
-
eps_0 (float): Peak strain of concrete in compression. Default
|
|
270
|
-
value = -0.002.
|
|
271
|
-
eps_u (float): Ultimate strain of concrete in compression. Default
|
|
272
|
-
value = -0.0035.
|
|
273
|
-
n (float): Exponent for the pre-peak branch. Default value = 2.
|
|
274
|
-
name (str): A name for the constitutive law.
|
|
275
|
-
"""
|
|
276
|
-
name = name if name is not None else 'ParabolaRectangleLaw'
|
|
277
|
-
super().__init__(name=name)
|
|
278
|
-
self._fc = -abs(fc)
|
|
279
|
-
self._eps_0 = -abs(eps_0)
|
|
280
|
-
self._eps_u = -abs(eps_u)
|
|
281
|
-
self._n = n
|
|
282
|
-
|
|
283
|
-
def get_stress(self, eps: ArrayLike) -> ArrayLike:
|
|
284
|
-
"""Return the stress given strain."""
|
|
285
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
286
|
-
# Preprocess eps array in order
|
|
287
|
-
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
288
|
-
# Compute stress
|
|
289
|
-
sig = np.zeros_like(eps)
|
|
290
|
-
# Parabolic branch
|
|
291
|
-
sig[(eps <= 0) & (eps >= self._eps_0)] = self._fc * (
|
|
292
|
-
1
|
|
293
|
-
- (1 - (eps[(eps <= 0) & (eps >= self._eps_0)] / self._eps_0))
|
|
294
|
-
** self._n
|
|
295
|
-
)
|
|
296
|
-
# Rectangle branch
|
|
297
|
-
sig[eps < self._eps_0] = self._fc
|
|
298
|
-
# Zero elsewhere
|
|
299
|
-
sig[eps < self._eps_u] = 0
|
|
300
|
-
sig[eps > 0] = 0
|
|
301
|
-
return sig
|
|
302
|
-
|
|
303
|
-
def get_tangent(self, eps: ArrayLike) -> ArrayLike:
|
|
304
|
-
"""Return the tangent given strain."""
|
|
305
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
306
|
-
# parabolic branch
|
|
307
|
-
tangent = np.zeros_like(eps)
|
|
308
|
-
tangent[(eps <= 0) & (eps >= self._eps_0)] = (
|
|
309
|
-
self._n
|
|
310
|
-
* self._fc
|
|
311
|
-
/ self._eps_0
|
|
312
|
-
* (1 - (eps[(eps <= 0) & (eps >= self._eps_0)] / self._eps_0))
|
|
313
|
-
** (self._n - 1)
|
|
314
|
-
)
|
|
315
|
-
# Elsewhere tangent is zero
|
|
316
|
-
tangent[eps < self._eps_0] = 0.0
|
|
317
|
-
tangent[eps > 0] = 0.0
|
|
318
|
-
return tangent
|
|
319
|
-
|
|
320
|
-
def __marin__(
|
|
321
|
-
self, strain: t.Tuple[float, float]
|
|
322
|
-
) -> t.Tuple[t.List[float], t.List[float]]:
|
|
323
|
-
"""Returns coefficients and strain limits for Marin integration in a
|
|
324
|
-
simply formatted way.
|
|
325
|
-
|
|
326
|
-
Arguments:
|
|
327
|
-
strain (float, float): Tuple defining the strain profile: eps =
|
|
328
|
-
strain[0] + strain[1]*y.
|
|
329
|
-
|
|
330
|
-
Example:
|
|
331
|
-
[(0, -0.002), (-0.002, -0.003)]
|
|
332
|
-
[(a0, a1, a2), (a0)]
|
|
333
|
-
"""
|
|
334
|
-
if self._n != 2:
|
|
335
|
-
# The constitutive law is not writtable as a polynomial,
|
|
336
|
-
# Call the generic distretizing method
|
|
337
|
-
return super().__marin__(strain=strain)
|
|
338
|
-
|
|
339
|
-
strains = []
|
|
340
|
-
coeff = []
|
|
341
|
-
if strain[1] == 0:
|
|
342
|
-
# Uniform strain equal to strain[0]
|
|
343
|
-
# understand in which branch are we
|
|
344
|
-
strain[0] = self.preprocess_strains_with_limits(strain[0])[0]
|
|
345
|
-
if strain[0] > 0:
|
|
346
|
-
# We are in tensile branch
|
|
347
|
-
strains = None
|
|
348
|
-
coeff.append((0.0,))
|
|
349
|
-
elif strain[0] > self._eps_0:
|
|
350
|
-
# We are in the parabolic branch
|
|
351
|
-
strains = None
|
|
352
|
-
a0 = (
|
|
353
|
-
2
|
|
354
|
-
* self._fc
|
|
355
|
-
* strain[0]
|
|
356
|
-
/ self._eps_0
|
|
357
|
-
* (1 - 0.5 * (strain[0] / self._eps_0))
|
|
358
|
-
)
|
|
359
|
-
a1 = (
|
|
360
|
-
2
|
|
361
|
-
* self._fc
|
|
362
|
-
/ self._eps_0
|
|
363
|
-
* strain[1]
|
|
364
|
-
* (1 - strain[0] / self._eps_0)
|
|
365
|
-
)
|
|
366
|
-
coeff.append((a0, a1, 0.0))
|
|
367
|
-
elif strain[0] >= self._eps_u:
|
|
368
|
-
# We are in the constant branch
|
|
369
|
-
strains = None
|
|
370
|
-
coeff.append((self._fc,))
|
|
371
|
-
else:
|
|
372
|
-
# We are in a branch of non-resisting concrete
|
|
373
|
-
# Too much compression
|
|
374
|
-
strains = None
|
|
375
|
-
coeff.append((0.0,))
|
|
376
|
-
else:
|
|
377
|
-
# Parabolic part
|
|
378
|
-
strains.append((self._eps_0, 0))
|
|
379
|
-
a0 = (
|
|
380
|
-
2
|
|
381
|
-
* self._fc
|
|
382
|
-
* strain[0]
|
|
383
|
-
/ self._eps_0
|
|
384
|
-
* (1 - 0.5 * (strain[0] / self._eps_0))
|
|
385
|
-
)
|
|
386
|
-
a1 = (
|
|
387
|
-
2
|
|
388
|
-
* self._fc
|
|
389
|
-
/ self._eps_0
|
|
390
|
-
* strain[1]
|
|
391
|
-
* (1 - strain[0] / self._eps_0)
|
|
392
|
-
)
|
|
393
|
-
a2 = -self._fc * strain[1] ** 2 / self._eps_0**2
|
|
394
|
-
coeff.append((a0, a1, a2))
|
|
395
|
-
# Constant part
|
|
396
|
-
strains.append((self._eps_u, self._eps_0))
|
|
397
|
-
coeff.append((self._fc,))
|
|
398
|
-
return strains, coeff
|
|
399
|
-
|
|
400
|
-
def get_ultimate_strain(
|
|
401
|
-
self, yielding: bool = False
|
|
402
|
-
) -> t.Tuple[float, float]:
|
|
403
|
-
"""Return the ultimate strain (negative and positive)."""
|
|
404
|
-
if yielding:
|
|
405
|
-
return (self._eps_0, 100)
|
|
406
|
-
return (self._eps_u, 100)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
class BilinearCompression(ConstitutiveLaw):
|
|
410
|
-
"""Class for Bilinear Elastic-PerfectlyPlastic Constitutive Law for
|
|
411
|
-
Concrete (only compression behavior).
|
|
412
|
-
"""
|
|
413
|
-
|
|
414
|
-
__materials__: t.Tuple[str] = ('concrete',)
|
|
415
|
-
|
|
416
|
-
def __init__(
|
|
417
|
-
self,
|
|
418
|
-
fc: float,
|
|
419
|
-
eps_c: float,
|
|
420
|
-
eps_cu: t.Optional[float] = None,
|
|
421
|
-
name: t.Optional[str] = None,
|
|
422
|
-
) -> None:
|
|
423
|
-
"""Initialize a BilinearCompression Material.
|
|
424
|
-
|
|
425
|
-
Arguments:
|
|
426
|
-
fc (float): Compressive strength (negative number).
|
|
427
|
-
eps_c (float): Strain at compressive strength (pure number).
|
|
428
|
-
|
|
429
|
-
Keyword Arguments:
|
|
430
|
-
eps_cu (float): Ultimate strain (pure number).
|
|
431
|
-
name (str): A descriptive name for the constitutive law.
|
|
432
|
-
"""
|
|
433
|
-
name = name if name is not None else 'BilinearCompressionLaw'
|
|
434
|
-
super().__init__(name=name)
|
|
435
|
-
self._fc = -abs(fc)
|
|
436
|
-
self._eps_c = -abs(eps_c)
|
|
437
|
-
self._eps_cu = -abs(eps_cu)
|
|
438
|
-
self._E = self._fc / self._eps_c
|
|
439
|
-
|
|
440
|
-
def get_stress(self, eps: ArrayLike) -> ArrayLike:
|
|
441
|
-
"""Return the stress given strain."""
|
|
442
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
443
|
-
# Preprocess eps array in order
|
|
444
|
-
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
445
|
-
# Compute stress
|
|
446
|
-
sig = self._E * eps
|
|
447
|
-
sig[sig < self._fc] = self._fc
|
|
448
|
-
sig[eps > 0] = 0
|
|
449
|
-
sig[eps < self._eps_cu] = 0
|
|
450
|
-
return sig
|
|
451
|
-
|
|
452
|
-
def get_tangent(self, eps: ArrayLike) -> ArrayLike:
|
|
453
|
-
"""Return the tangent for given strain."""
|
|
454
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
455
|
-
tangent = np.ones_like(eps) * self._E
|
|
456
|
-
tangent[eps < self._eps_c] = 0.0
|
|
457
|
-
|
|
458
|
-
return tangent
|
|
459
|
-
|
|
460
|
-
def __marin__(
|
|
461
|
-
self, strain: t.Tuple[float, float]
|
|
462
|
-
) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
|
|
463
|
-
"""Returns coefficients and strain limits for Marin integration in a
|
|
464
|
-
simply formatted way.
|
|
465
|
-
|
|
466
|
-
Arguments:
|
|
467
|
-
strain (float, float): Tuple defining the strain profile: eps =
|
|
468
|
-
strain[0] + strain[1]*y.
|
|
469
|
-
|
|
470
|
-
Example:
|
|
471
|
-
[(0, -0.002), (-0.002, -0.003)]
|
|
472
|
-
[(a0, a1, a2), (a0)]
|
|
473
|
-
"""
|
|
474
|
-
strains = []
|
|
475
|
-
coeff = []
|
|
476
|
-
if strain[1] == 0:
|
|
477
|
-
# Uniform strain equal to strain[0]
|
|
478
|
-
# understand in which branch we are
|
|
479
|
-
strain[0] = self.preprocess_strains_with_limits(strain[0])[0]
|
|
480
|
-
if strain[0] > 0:
|
|
481
|
-
# We are in tensile branch
|
|
482
|
-
strains = None
|
|
483
|
-
coeff.append((0.0,))
|
|
484
|
-
elif strain[0] > self._eps_0:
|
|
485
|
-
# We are in the linear branch
|
|
486
|
-
strains = None
|
|
487
|
-
a0 = self._E * strain[0]
|
|
488
|
-
a1 = self._E * strain[1]
|
|
489
|
-
coeff.append((a0, a1))
|
|
490
|
-
elif strain[0] >= self._eps_cu:
|
|
491
|
-
# We are in the constant branch
|
|
492
|
-
strains = None
|
|
493
|
-
coeff.append((self._fc,))
|
|
494
|
-
else:
|
|
495
|
-
# We are in a branch of non-resisting concrete
|
|
496
|
-
# Too much compression
|
|
497
|
-
strains = None
|
|
498
|
-
coeff.append((0.0,))
|
|
499
|
-
else:
|
|
500
|
-
# linear part
|
|
501
|
-
strains.append((self._eps_c, 0))
|
|
502
|
-
a0 = self._E * strain[0]
|
|
503
|
-
a1 = self._E * strain[1]
|
|
504
|
-
coeff.append((a0, a1))
|
|
505
|
-
# Constant part
|
|
506
|
-
strains.append((self._eps_cu, self._eps_c))
|
|
507
|
-
coeff.append((self._fc,))
|
|
508
|
-
return strains, coeff
|
|
509
|
-
|
|
510
|
-
def get_ultimate_strain(
|
|
511
|
-
self, yielding: bool = False
|
|
512
|
-
) -> t.Tuple[float, float]:
|
|
513
|
-
"""Return the ultimate strain (negative and positive)."""
|
|
514
|
-
if yielding:
|
|
515
|
-
return (self._eps_c, 100)
|
|
516
|
-
return (self._eps_cu, 100)
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
class Sargin(ConstitutiveLaw):
|
|
520
|
-
"""Class for Sargin constitutive law.
|
|
521
|
-
|
|
522
|
-
The stresses and strains are assumed negative in compression and positive
|
|
523
|
-
in tension.
|
|
524
|
-
|
|
525
|
-
References:
|
|
526
|
-
Sargin, M. (1971), "Stress-strain relationship for concrete and the
|
|
527
|
-
analysis of structural concrete section, Study No. 4,
|
|
528
|
-
Solid Mechanics Division, University of Waterloo, Ontario, Canada
|
|
529
|
-
"""
|
|
530
|
-
|
|
531
|
-
__materials__: t.Tuple[str] = ('concrete',)
|
|
532
|
-
|
|
533
|
-
def __init__(
|
|
534
|
-
self,
|
|
535
|
-
fc: float,
|
|
536
|
-
eps_c1: float = -0.0023,
|
|
537
|
-
eps_cu1: float = -0.0035,
|
|
538
|
-
k: float = 2.04,
|
|
539
|
-
name: t.Optional[str] = None,
|
|
540
|
-
) -> None:
|
|
541
|
-
"""Initialize a Sargin Material.
|
|
542
|
-
|
|
543
|
-
Arguments:
|
|
544
|
-
fc (float): The strength of concrete in compression
|
|
545
|
-
|
|
546
|
-
Keyword Arguments:
|
|
547
|
-
eps_c1 (float): Peak strain of concrete in compression. Default
|
|
548
|
-
value = -0.0023.
|
|
549
|
-
eps_u (float): Ultimate strain of concrete in compression. Default
|
|
550
|
-
value = -0.0035.
|
|
551
|
-
k (float): Plasticity number. Default value = 2.04.
|
|
552
|
-
name (str): A name for the constitutive law.
|
|
553
|
-
|
|
554
|
-
Raises:
|
|
555
|
-
ValueError: If k is less or equal to 0.
|
|
556
|
-
|
|
557
|
-
Note:
|
|
558
|
-
If positive values are input for fc, eps_c1 and eps_cu1 are input,
|
|
559
|
-
they will be assumed negative.
|
|
560
|
-
"""
|
|
561
|
-
name = name if name is not None else 'SarginLaw'
|
|
562
|
-
super().__init__(name=name)
|
|
563
|
-
self._fc = -abs(fc)
|
|
564
|
-
self._eps_c1 = -abs(eps_c1)
|
|
565
|
-
self._eps_cu1 = -abs(eps_cu1)
|
|
566
|
-
self._k = k
|
|
567
|
-
|
|
568
|
-
def get_stress(self, eps: ArrayLike) -> ArrayLike:
|
|
569
|
-
"""Return the stress given the strain."""
|
|
570
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
571
|
-
# Preprocess eps array in order
|
|
572
|
-
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
573
|
-
# Compute stress
|
|
574
|
-
# Polynomial branch
|
|
575
|
-
eta = eps / self._eps_c1
|
|
576
|
-
|
|
577
|
-
sig = self._fc * (self._k * eta - eta**2) / (1 + (self._k - 2) * eta)
|
|
578
|
-
|
|
579
|
-
# Elsewhere stress is 0.0
|
|
580
|
-
sig[eps < self._eps_cu1] = 0.0
|
|
581
|
-
sig[eps > 0] = 0.0
|
|
582
|
-
|
|
583
|
-
return sig
|
|
584
|
-
|
|
585
|
-
def get_tangent(self, eps: ArrayLike) -> ArrayLike:
|
|
586
|
-
"""Return the tangent given strain."""
|
|
587
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
588
|
-
# polynomial branch
|
|
589
|
-
eta = eps / self._eps_c1
|
|
590
|
-
|
|
591
|
-
tangent = (
|
|
592
|
-
self._fc
|
|
593
|
-
/ self._eps_c1
|
|
594
|
-
* ((2 - self._k) * eta**2 - 2 * eta + self._k)
|
|
595
|
-
/ (1 + (self._k - 2) * eta) ** 2
|
|
596
|
-
)
|
|
597
|
-
# Elsewhere tangent is zero
|
|
598
|
-
tangent[eps < self._eps_cu1] = 0.0
|
|
599
|
-
tangent[eps > 0] = 0.0
|
|
600
|
-
|
|
601
|
-
return tangent
|
|
602
|
-
|
|
603
|
-
def get_ultimate_strain(
|
|
604
|
-
self, yielding: bool = False
|
|
605
|
-
) -> t.Tuple[float, float]:
|
|
606
|
-
"""Return the ultimate strain (negative and positive)."""
|
|
607
|
-
if yielding:
|
|
608
|
-
return (self._eps_c1, 100)
|
|
609
|
-
return (self._eps_cu1, 100)
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
class Popovics(ConstitutiveLaw):
|
|
613
|
-
"""Class for Popovics-Mander constitutive law.
|
|
614
|
-
|
|
615
|
-
The stresses and strains are assumed negative in compression and positive
|
|
616
|
-
in tension.
|
|
617
|
-
|
|
618
|
-
If the relation Ec = 5000 * sqrt(fc) is used for elastic modulus, the
|
|
619
|
-
constitutive law is identical to the one proposed by Mander et al. (1988).
|
|
620
|
-
|
|
621
|
-
References:
|
|
622
|
-
Popovics, S., 1973, “A Numerical Approach to the Complete Stress-Strain
|
|
623
|
-
Curve of Concrete”, Cement and Concrete Research, 3(4), 583-599.
|
|
624
|
-
|
|
625
|
-
Mander, J.B., Priestley, M.J.N., Park, R., 1988, "Theoretical Stress-Strain
|
|
626
|
-
Model for Confined Concrete", Journal of Structural Engineering, 114(8),
|
|
627
|
-
1804-1826.
|
|
628
|
-
"""
|
|
629
|
-
|
|
630
|
-
__materials__: t.Tuple[str] = ('concrete',)
|
|
631
|
-
|
|
632
|
-
def __init__(
|
|
633
|
-
self,
|
|
634
|
-
fc: float,
|
|
635
|
-
eps_c: float = -0.002,
|
|
636
|
-
eps_cu: float = -0.0035,
|
|
637
|
-
Ec: t.Optional[float] = None,
|
|
638
|
-
name: t.Optional[str] = None,
|
|
639
|
-
) -> None:
|
|
640
|
-
"""Initialize a Popovics Material.
|
|
641
|
-
|
|
642
|
-
Arguments:
|
|
643
|
-
fc (float): the strength of concrete in compression
|
|
644
|
-
|
|
645
|
-
Keyword Arguments:
|
|
646
|
-
eps_c (float): Peak strain of concrete in compression. Default
|
|
647
|
-
value = -0.002.
|
|
648
|
-
eps_cu (float): Ultimate strain of concrete in compression. Default
|
|
649
|
-
value = -0.0035.
|
|
650
|
-
E (optional float): Elastic modulus of concrete. If None, the
|
|
651
|
-
equation Ec = 5000 * fc**0.5 proposed by Mander et al. (1988)
|
|
652
|
-
is adopted (fc in MPa). Default value = None.
|
|
653
|
-
name (str): A name for the constitutive law.
|
|
654
|
-
|
|
655
|
-
Raises:
|
|
656
|
-
ValueError: If E is less or equal to 0.
|
|
657
|
-
|
|
658
|
-
Note:
|
|
659
|
-
If positive values are input for fc, eps_c and eps_cu are input,
|
|
660
|
-
they will be assumed negative.
|
|
661
|
-
"""
|
|
662
|
-
name = name if name is not None else 'PopovicsLaw'
|
|
663
|
-
super().__init__(name=name)
|
|
664
|
-
self._fc = -abs(fc)
|
|
665
|
-
self._eps_c = -abs(eps_c)
|
|
666
|
-
self._eps_cu = -abs(eps_cu)
|
|
667
|
-
if Ec is None:
|
|
668
|
-
# fc in MPa, relation of Mander et al. (1988)
|
|
669
|
-
Ec = 5000 * abs(fc) ** 0.5
|
|
670
|
-
if Ec <= 0:
|
|
671
|
-
raise ValueError('Elastic modulus must be a positive number.')
|
|
672
|
-
E_sec = self._fc / self._eps_c
|
|
673
|
-
self._n = Ec / (Ec - E_sec)
|
|
674
|
-
|
|
675
|
-
def get_stress(self, eps: ArrayLike) -> ArrayLike:
|
|
676
|
-
"""Return the stress given the strain."""
|
|
677
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
678
|
-
# Preprocess eps array in order
|
|
679
|
-
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
680
|
-
# Compute stress
|
|
681
|
-
# Compression branch
|
|
682
|
-
eta = eps / self._eps_c
|
|
683
|
-
|
|
684
|
-
sig = self._fc * eta * self._n / (self._n - 1 + eta**self._n)
|
|
685
|
-
|
|
686
|
-
# Elsewhere stress is 0.0
|
|
687
|
-
sig[eps < self._eps_cu] = 0.0
|
|
688
|
-
sig[eps > 0] = 0.0
|
|
689
|
-
|
|
690
|
-
return sig
|
|
691
|
-
|
|
692
|
-
def get_tangent(self, eps: ArrayLike) -> ArrayLike:
|
|
693
|
-
"""Return the tangent given strain."""
|
|
694
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
695
|
-
# Preprocess eps array in order
|
|
696
|
-
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
697
|
-
# Compression branch
|
|
698
|
-
eta = eps / self._eps_c
|
|
699
|
-
|
|
700
|
-
tangent = (
|
|
701
|
-
(1 - eta**self._n)
|
|
702
|
-
/ (self._n - 1 + eta**self._n) ** 2
|
|
703
|
-
* self._n
|
|
704
|
-
* (self._n - 1)
|
|
705
|
-
* self._fc
|
|
706
|
-
/ self._eps_c
|
|
707
|
-
)
|
|
708
|
-
# Elsewhere tangent is zero
|
|
709
|
-
tangent[eps < self._eps_cu] = 0.0
|
|
710
|
-
tangent[eps > 0] = 0.0
|
|
711
|
-
|
|
712
|
-
return tangent
|
|
713
|
-
|
|
714
|
-
def get_ultimate_strain(
|
|
715
|
-
self, yielding: bool = False
|
|
716
|
-
) -> t.Tuple[float, float]:
|
|
717
|
-
"""Return the ultimate strain (negative and positive)."""
|
|
718
|
-
if yielding:
|
|
719
|
-
return (self._eps_c, 100)
|
|
720
|
-
return (self._eps_cu, 100)
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
class UserDefined(ConstitutiveLaw):
|
|
724
|
-
"""Class for a user defined constitutive law.
|
|
725
|
-
|
|
726
|
-
The curve is defined with positive and optionally negative values. After
|
|
727
|
-
the last value, the stress can go to zero to simulate failure (default), or
|
|
728
|
-
be maintained constante, or the last tanget or secant values may be
|
|
729
|
-
maintained indefinetely. The flag parameter controls this behavior.
|
|
730
|
-
"""
|
|
731
|
-
|
|
732
|
-
__materials__: t.Tuple[str] = ('concrete', 'steel', 'rebars')
|
|
733
|
-
|
|
734
|
-
def __init__(
|
|
735
|
-
self,
|
|
736
|
-
x: ArrayLike,
|
|
737
|
-
y: ArrayLike,
|
|
738
|
-
name: t.Optional[str] = None,
|
|
739
|
-
flag: int = 0,
|
|
740
|
-
) -> None:
|
|
741
|
-
"""Initialize a UserDefined constitutive law.
|
|
742
|
-
|
|
743
|
-
Arguments:
|
|
744
|
-
x (ArrayLike): Data for strains.
|
|
745
|
-
y (ArrayLike): Data for stresses. Must be of same length as x.
|
|
746
|
-
|
|
747
|
-
Keyword Arguments:
|
|
748
|
-
name (Optional, str): A name for the constitutive law.
|
|
749
|
-
flag (Optional): A flag specifying the behavior after the last
|
|
750
|
-
point. Admissible values: 0 (default): stress drops to zero
|
|
751
|
-
after ultimate strain, 1: stress is mantained constant, 2:
|
|
752
|
-
last tangent is used, 3: last secant is used.
|
|
753
|
-
"""
|
|
754
|
-
name = name if name is not None else 'UserDefinedLaw'
|
|
755
|
-
super().__init__(name=name)
|
|
756
|
-
x = np.atleast_1d(np.asarray(x))
|
|
757
|
-
y = np.atleast_1d(np.asarray(y))
|
|
758
|
-
if len(x) != len(y):
|
|
759
|
-
raise ValueError('The two arrays should have the same length')
|
|
760
|
-
if not np.any(x < 0):
|
|
761
|
-
# User provided only positive part, reflect in negative
|
|
762
|
-
self._x = np.concatenate((-np.flip(x)[:-1], x))
|
|
763
|
-
self._y = np.concatenate((-np.flip(y)[:-1], y))
|
|
764
|
-
else:
|
|
765
|
-
# User gave both positive and negative parts
|
|
766
|
-
self._x = x
|
|
767
|
-
self._y = y
|
|
768
|
-
# Define what happens after last strain
|
|
769
|
-
if flag not in (0, 1, 2, 3):
|
|
770
|
-
raise ValueError('Flag can assume values 0, 1, 2 or 3.')
|
|
771
|
-
self._ultimate_strain_p = self._x[-1]
|
|
772
|
-
self._ultimate_strain_n = self._x[0]
|
|
773
|
-
if flag in (1, 2, 3):
|
|
774
|
-
x = np.insert(self._x, 0, self._x[0] * 100)
|
|
775
|
-
x = np.append(x, self._x[-1] * 100)
|
|
776
|
-
if flag == 1:
|
|
777
|
-
y = np.insert(self._y, 0, self._y[0])
|
|
778
|
-
y = np.append(y, self._y[-1])
|
|
779
|
-
elif flag == 2:
|
|
780
|
-
tangent_p = (self._y[-1] - self._y[-2]) / (
|
|
781
|
-
self._x[-1] - self._x[-2]
|
|
782
|
-
)
|
|
783
|
-
tangent_n = (self._y[1] - self._y[0]) / (
|
|
784
|
-
self._x[1] - self._x[0]
|
|
785
|
-
)
|
|
786
|
-
y = np.insert(
|
|
787
|
-
self._y, 0, (x[0] - x[1]) * tangent_n + self._y[0]
|
|
788
|
-
)
|
|
789
|
-
y = np.append(y, (x[-1] - x[-2]) * tangent_p + self._y[-1])
|
|
790
|
-
elif flag == 3:
|
|
791
|
-
secant_p = self._y[-1] / self._x[-1]
|
|
792
|
-
secant_n = self._y[0] / self._x[0]
|
|
793
|
-
y = np.insert(
|
|
794
|
-
self._y, 0, (x[0] - x[1]) * secant_n + self._y[0]
|
|
795
|
-
)
|
|
796
|
-
y = np.append(y, (x[-1] - x[-2]) * secant_p + self._y[-1])
|
|
797
|
-
self._x = x
|
|
798
|
-
self._y = y
|
|
799
|
-
|
|
800
|
-
# Compute slope of each segment
|
|
801
|
-
self._slopes = np.diff(self._y) / np.diff(self._x)
|
|
802
|
-
|
|
803
|
-
def get_stress(self, eps: ArrayLike) -> ArrayLike:
|
|
804
|
-
"""Return the stress given strain."""
|
|
805
|
-
eps = np.atleast_1d(np.asarray(eps))
|
|
806
|
-
# Preprocess eps array in order
|
|
807
|
-
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
808
|
-
# Compute stress
|
|
809
|
-
return np.interp(eps, self._x, self._y, left=0, right=0)
|
|
810
|
-
|
|
811
|
-
def get_tangent(self, eps: ArrayLike) -> ArrayLike:
|
|
812
|
-
"""Return the tangent given strain."""
|
|
813
|
-
eps = np.atleast_1d(np.array(eps))
|
|
814
|
-
|
|
815
|
-
# Find the segment index for each x value
|
|
816
|
-
indices = np.searchsorted(self._x, eps) - 1
|
|
817
|
-
|
|
818
|
-
# Check that indices are within vlaid range
|
|
819
|
-
indices = np.clip(indices, 0, len(self._slopes) - 1)
|
|
820
|
-
|
|
821
|
-
# Get the corresponding slopes
|
|
822
|
-
tangent = self._slopes[indices]
|
|
823
|
-
|
|
824
|
-
# Elsewhere tangent is zero
|
|
825
|
-
tangent[eps < self._x[0]] = 0.0
|
|
826
|
-
tangent[eps > self._x[-1]] = 0.0
|
|
827
|
-
|
|
828
|
-
return tangent
|
|
829
|
-
|
|
830
|
-
def __marin__(
|
|
831
|
-
self, strain: t.Tuple[float, float]
|
|
832
|
-
) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
|
|
833
|
-
"""Returns coefficients and strain limits for Marin integration in a
|
|
834
|
-
simply formatted way.
|
|
835
|
-
|
|
836
|
-
Arguments:
|
|
837
|
-
strain (float, float): Tuple defining the strain profile: eps =
|
|
838
|
-
strain[0] + strain[1]*y.
|
|
839
|
-
|
|
840
|
-
Example:
|
|
841
|
-
[(0, -0.002), (-0.002, -0.003)]
|
|
842
|
-
[(a0, a1, a2), (a0)]
|
|
843
|
-
"""
|
|
844
|
-
strains = []
|
|
845
|
-
coeff = []
|
|
846
|
-
if strain[1] == 0:
|
|
847
|
-
# Uniform strain equal to strain[0]
|
|
848
|
-
# understand in which branch are we
|
|
849
|
-
strain[0] = self.preprocess_strains_with_limits(strain[0])[0]
|
|
850
|
-
found = False
|
|
851
|
-
for i in range(len(self._x) - 1):
|
|
852
|
-
if self._x[i] <= strain[0] and self._x[i + 1] >= strain[0]:
|
|
853
|
-
strains = None
|
|
854
|
-
stiffness = (self._y[i + 1] - self._y[i]) / (
|
|
855
|
-
self._x[i + 1] - self._x[i]
|
|
856
|
-
)
|
|
857
|
-
a0 = stiffness * (strain[0] - self._x[i]) + self._y[i]
|
|
858
|
-
a1 = stiffness * strain[1]
|
|
859
|
-
coeff.append((a0, a1))
|
|
860
|
-
found = True
|
|
861
|
-
break
|
|
862
|
-
if not found:
|
|
863
|
-
strains = None
|
|
864
|
-
coeff.append((0.0,))
|
|
865
|
-
else:
|
|
866
|
-
for i in range(len(self._x) - 1):
|
|
867
|
-
# For each branch of the linear piecewise function
|
|
868
|
-
stiffness = (self._y[i + 1] - self._y[i]) / (
|
|
869
|
-
self._x[i + 1] - self._x[i]
|
|
870
|
-
)
|
|
871
|
-
strains.append((self._x[i], self._x[i + 1]))
|
|
872
|
-
a0 = stiffness * (strain[0] - self._x[i]) + self._y[i]
|
|
873
|
-
a1 = stiffness * strain[1]
|
|
874
|
-
coeff.append((a0, a1))
|
|
875
|
-
|
|
876
|
-
return strains, coeff
|
|
877
|
-
|
|
878
|
-
def get_ultimate_strain(self, **kwargs) -> t.Tuple[float, float]:
|
|
879
|
-
"""Return the ultimate strain (negative and positive)."""
|
|
880
|
-
del kwargs
|
|
881
|
-
return (self._ultimate_strain_n, self._ultimate_strain_p)
|
|
882
|
-
|
|
883
|
-
def set_ultimate_strain(
|
|
884
|
-
self, eps_su=t.Union[float, t.Tuple[float, float]]
|
|
885
|
-
) -> None:
|
|
886
|
-
"""Set ultimate strains for Elastic Material if needed.
|
|
887
|
-
|
|
888
|
-
Arguments:
|
|
889
|
-
eps_su (float or (float, float)): Defining ultimate strain. If a
|
|
890
|
-
single value is provided the same is adopted for both negative
|
|
891
|
-
and positive strains. If a tuple is provided, it should be
|
|
892
|
-
given as (negative, positive).
|
|
893
|
-
"""
|
|
894
|
-
if isinstance(eps_su, float):
|
|
895
|
-
self._ultimate_strain_p = abs(eps_su)
|
|
896
|
-
self._ultimate_strain_n = -abs(eps_su)
|
|
897
|
-
elif isinstance(eps_su, tuple):
|
|
898
|
-
if len(eps_su) < 2:
|
|
899
|
-
raise ValueError(
|
|
900
|
-
'Two values need to be provided when setting the tuple'
|
|
901
|
-
)
|
|
902
|
-
eps_su_n = eps_su[0]
|
|
903
|
-
eps_su_p = eps_su[1]
|
|
904
|
-
if eps_su_p < eps_su_n:
|
|
905
|
-
eps_su_p, eps_su_n = eps_su_n, eps_su_p
|
|
906
|
-
if eps_su_p < 0:
|
|
907
|
-
raise ValueError(
|
|
908
|
-
'Positive ultimate strain should be non-negative'
|
|
909
|
-
)
|
|
910
|
-
if eps_su_n > 0:
|
|
911
|
-
raise ValueError(
|
|
912
|
-
'Negative utimate strain should be non-positive'
|
|
913
|
-
)
|
|
914
|
-
self._ultimate_strain_p = eps_su_p
|
|
915
|
-
self._ultimate_strain_n = eps_su_n
|
|
916
|
-
else:
|
|
917
|
-
raise ValueError(
|
|
918
|
-
'set_ultimate_strain requires a single value or a tuple \
|
|
919
|
-
with two values'
|
|
920
|
-
)
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
CONSTITUTIVE_LAWS: t.Dict[str, ConstitutiveLaw] = {
|
|
924
|
-
'elastic': Elastic,
|
|
925
|
-
'elasticplastic': ElasticPlastic,
|
|
926
|
-
'elasticperfectlyplastic': ElasticPlastic,
|
|
927
|
-
'bilinearcompression': BilinearCompression,
|
|
928
|
-
'parabolarectangle': ParabolaRectangle,
|
|
929
|
-
'popovics': Popovics,
|
|
930
|
-
'sargin': Sargin,
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
def get_constitutive_laws_list() -> t.List[str]:
|
|
935
|
-
"""Returns a list with valid keywords for constitutive law factory."""
|
|
936
|
-
return list(CONSTITUTIVE_LAWS.keys())
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
def create_constitutive_law(
|
|
940
|
-
constitutive_law_name: str, material: Material
|
|
941
|
-
) -> ConstitutiveLaw:
|
|
942
|
-
"""A factory function to create the constitutive law.
|
|
943
|
-
|
|
944
|
-
Arguments:
|
|
945
|
-
constitutive_law_name (str): A string defining a valid constitutive law
|
|
946
|
-
type. The available keys can be get with the method
|
|
947
|
-
`get_constitutive_laws_list`.
|
|
948
|
-
material (Material): The material containing the properties needed for
|
|
949
|
-
the definition of the constitutive law.
|
|
950
|
-
|
|
951
|
-
Note:
|
|
952
|
-
For working with this facotry function, the material class
|
|
953
|
-
implementations need to provide special dunder methods (__elastic__,
|
|
954
|
-
__parabolarectangle__, etc.) needed for the specific material that
|
|
955
|
-
return the kwargs needed to create the corresponding constitutive
|
|
956
|
-
law object. If the special dunder method is not found an exception
|
|
957
|
-
will be raised.
|
|
958
|
-
|
|
959
|
-
If the consitutive law selected is not available for the specific
|
|
960
|
-
material, an exception will be raised.
|
|
961
|
-
"""
|
|
962
|
-
law = None
|
|
963
|
-
const_law = CONSTITUTIVE_LAWS.get(constitutive_law_name.lower())
|
|
964
|
-
if const_law is not None:
|
|
965
|
-
method_name = f'__{constitutive_law_name}__'
|
|
966
|
-
# check if the material object has the special method needed
|
|
967
|
-
if hasattr(material, method_name):
|
|
968
|
-
method = getattr(material, method_name)
|
|
969
|
-
if callable(method):
|
|
970
|
-
# get the kwargs from the special dunder method
|
|
971
|
-
kwargs = method()
|
|
972
|
-
# create the constitutive law
|
|
973
|
-
law = const_law(**kwargs)
|
|
974
|
-
else:
|
|
975
|
-
raise ValueError(
|
|
976
|
-
f'Constitutive law {constitutive_law_name} not available for'
|
|
977
|
-
f' material {material.__class__.__name__}'
|
|
978
|
-
)
|
|
979
|
-
else:
|
|
980
|
-
raise ValueError(f'Unknown constitutive law: {constitutive_law_name}')
|
|
981
|
-
return law
|