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,227 @@
|
|
|
1
|
+
"""Elastic-plastic 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 ElasticPlastic(ConstitutiveLaw):
|
|
14
|
+
"""Class for elastic-plastic Constitutive Law."""
|
|
15
|
+
|
|
16
|
+
__materials__: t.Tuple[str] = (
|
|
17
|
+
'steel',
|
|
18
|
+
'rebars',
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
E: float,
|
|
24
|
+
fy: float,
|
|
25
|
+
Eh: float = 0.0,
|
|
26
|
+
eps_su: t.Optional[float] = None,
|
|
27
|
+
name: t.Optional[str] = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Initialize an Elastic-Plastic Material.
|
|
30
|
+
|
|
31
|
+
Arguments:
|
|
32
|
+
E (float): The elastic modulus.
|
|
33
|
+
fy (float): The yield strength.
|
|
34
|
+
|
|
35
|
+
Keyword Arguments:
|
|
36
|
+
Eh (float): The hardening modulus.
|
|
37
|
+
eps_su (float): The ultimate strain.
|
|
38
|
+
name (str): A descriptive name for the constitutive law.
|
|
39
|
+
"""
|
|
40
|
+
name = name if name is not None else 'ElasticPlasticLaw'
|
|
41
|
+
super().__init__(name=name)
|
|
42
|
+
if E > 0:
|
|
43
|
+
self._E = E
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError('Elastic modulus E must be greater than zero')
|
|
46
|
+
self._fy = fy
|
|
47
|
+
self._Eh = Eh
|
|
48
|
+
self._eps_su = eps_su
|
|
49
|
+
self._eps_sy = fy / E
|
|
50
|
+
|
|
51
|
+
def get_stress(
|
|
52
|
+
self, eps: t.Union[float, ArrayLike]
|
|
53
|
+
) -> t.Union[float, ArrayLike]:
|
|
54
|
+
"""Return the stress given strain."""
|
|
55
|
+
eps = eps if np.isscalar(eps) else np.atleast_1d(eps)
|
|
56
|
+
# Preprocess eps array in order
|
|
57
|
+
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
58
|
+
# Compute stress
|
|
59
|
+
sig = self._E * eps
|
|
60
|
+
delta_sig = self._fy * (1 - self._Eh / self._E)
|
|
61
|
+
if np.isscalar(sig):
|
|
62
|
+
if sig < -self._fy:
|
|
63
|
+
sig = eps * self._Eh - delta_sig
|
|
64
|
+
if sig > self._fy:
|
|
65
|
+
sig = eps * self._Eh + delta_sig
|
|
66
|
+
if (self._eps_su is not None) and (
|
|
67
|
+
eps > self._eps_su or eps < -self._eps_su
|
|
68
|
+
):
|
|
69
|
+
sig = 0
|
|
70
|
+
return sig
|
|
71
|
+
sig[sig < -self._fy] = eps[sig < -self._fy] * self._Eh - delta_sig
|
|
72
|
+
sig[sig > self._fy] = eps[sig > self._fy] * self._Eh + delta_sig
|
|
73
|
+
if self._eps_su is not None:
|
|
74
|
+
sig[eps > self._eps_su] = 0
|
|
75
|
+
sig[eps < -self._eps_su] = 0 # pylint: disable=E1130
|
|
76
|
+
return sig
|
|
77
|
+
|
|
78
|
+
def get_tangent(
|
|
79
|
+
self, eps: t.Union[float, ArrayLike]
|
|
80
|
+
) -> t.Union[float, ArrayLike]:
|
|
81
|
+
"""Return the tangent for given strain."""
|
|
82
|
+
if np.isscalar(eps):
|
|
83
|
+
tangent = (
|
|
84
|
+
self._E if -self._eps_sy <= eps <= self._eps_sy else self._Eh
|
|
85
|
+
)
|
|
86
|
+
if (self._eps_su is not None) and (
|
|
87
|
+
eps > self._eps_su or eps < -self._eps_su
|
|
88
|
+
):
|
|
89
|
+
tangent = 0
|
|
90
|
+
return tangent
|
|
91
|
+
|
|
92
|
+
eps = np.atleast_1d(eps)
|
|
93
|
+
tangent = np.ones_like(eps) * self._E
|
|
94
|
+
tangent[eps > self._eps_sy] = self._Eh
|
|
95
|
+
tangent[eps < -self._eps_sy] = self._Eh
|
|
96
|
+
if self._eps_su is not None:
|
|
97
|
+
tangent[eps > self._eps_su] = 0
|
|
98
|
+
tangent[eps < -self._eps_su] = 0 # pylint: disable=E1130
|
|
99
|
+
|
|
100
|
+
return tangent
|
|
101
|
+
|
|
102
|
+
def __marin__(
|
|
103
|
+
self, strain: t.Tuple[float, float]
|
|
104
|
+
) -> t.Tuple[t.List[t.Tuple], t.List[t.Tuple]]:
|
|
105
|
+
"""Returns coefficients and strain limits for Marin integration in a
|
|
106
|
+
simply formatted way.
|
|
107
|
+
|
|
108
|
+
Arguments:
|
|
109
|
+
strain (float, float): Tuple defining the strain profile: eps =
|
|
110
|
+
strain[0] + strain[1]*y.
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
[(0, -0.002), (-0.002, -0.003)]
|
|
114
|
+
[(a0, a1, a2), (a0)]
|
|
115
|
+
"""
|
|
116
|
+
strains = []
|
|
117
|
+
coeff = []
|
|
118
|
+
eps_sy_n, eps_sy_p = self.get_ultimate_strain(yielding=True)
|
|
119
|
+
eps_su_n, eps_su_p = self.get_ultimate_strain()
|
|
120
|
+
if strain[1] == 0:
|
|
121
|
+
# Uniform strain equal to strain[0]
|
|
122
|
+
# Understand in which branch are we
|
|
123
|
+
strain[0] = self.preprocess_strains_with_limits(strain[0])
|
|
124
|
+
if strain[0] > eps_sy_p and strain[0] <= eps_su_p:
|
|
125
|
+
# We are in the Hardening part positive
|
|
126
|
+
strains = None
|
|
127
|
+
a0 = self._Eh * strain[0] + self._fy * (1 - self._Eh / self._E)
|
|
128
|
+
a1 = self._Eh * strain[1]
|
|
129
|
+
coeff.append((a0, a1))
|
|
130
|
+
elif strain[0] < eps_sy_n and strain[0] >= eps_su_n:
|
|
131
|
+
# We are in the Hardening part negative
|
|
132
|
+
strains = None
|
|
133
|
+
a0 = self._Eh * strain[0] - self._fy * (1 - self._Eh / self._E)
|
|
134
|
+
a1 = self._Eh * strain[1]
|
|
135
|
+
coeff.append((a0, a1))
|
|
136
|
+
elif abs(strain[0]) <= self._eps_sy:
|
|
137
|
+
# We are in the elastic part
|
|
138
|
+
strains = None
|
|
139
|
+
a0 = self._E * strain[0]
|
|
140
|
+
a1 = self._E * strain[1]
|
|
141
|
+
coeff.append((a0, a1))
|
|
142
|
+
else:
|
|
143
|
+
strains = None
|
|
144
|
+
coeff.append((0.0,))
|
|
145
|
+
else:
|
|
146
|
+
# Hardening part negative
|
|
147
|
+
strains.append((eps_su_n, eps_sy_n))
|
|
148
|
+
a0 = self._Eh * strain[0] - self._fy * (1 - self._Eh / self._E)
|
|
149
|
+
a1 = self._Eh * strain[1]
|
|
150
|
+
coeff.append((a0, a1))
|
|
151
|
+
# Elastic part
|
|
152
|
+
strains.append((eps_sy_n, eps_sy_p))
|
|
153
|
+
a0 = self._E * strain[0]
|
|
154
|
+
a1 = self._E * strain[1]
|
|
155
|
+
coeff.append((a0, a1))
|
|
156
|
+
# Hardening part positive
|
|
157
|
+
strains.append((eps_sy_p, eps_su_p))
|
|
158
|
+
a0 = self._Eh * strain[0] + self._fy * (1 - self._Eh / self._E)
|
|
159
|
+
a1 = self._Eh * strain[1]
|
|
160
|
+
coeff.append((a0, a1))
|
|
161
|
+
return strains, coeff
|
|
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
|
+
|
|
218
|
+
def get_ultimate_strain(
|
|
219
|
+
self, yielding: bool = False
|
|
220
|
+
) -> t.Tuple[float, float]:
|
|
221
|
+
"""Return the ultimate strain (negative and positive)."""
|
|
222
|
+
if yielding:
|
|
223
|
+
return (-self._eps_sy, self._eps_sy)
|
|
224
|
+
# If not specified eps
|
|
225
|
+
if self._eps_su is None:
|
|
226
|
+
return (-self._eps_sy * 2, self._eps_sy * 2)
|
|
227
|
+
return (-self._eps_su, self._eps_su)
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""Parabola-Rectangle 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 ParabolaRectangle(ConstitutiveLaw):
|
|
14
|
+
"""Class for parabola rectangle constitutive law.
|
|
15
|
+
|
|
16
|
+
The stresses and strains are assumed negative in compression and positive
|
|
17
|
+
in tension.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
__materials__: t.Tuple[str] = ('concrete',)
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
fc: float,
|
|
25
|
+
eps_0: float = -0.002,
|
|
26
|
+
eps_u: float = -0.0035,
|
|
27
|
+
n: float = 2.0,
|
|
28
|
+
name: t.Optional[str] = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Initialize a Parabola-Rectangle Material.
|
|
31
|
+
|
|
32
|
+
Arguments:
|
|
33
|
+
fc (float): The strength of concrete in compression.
|
|
34
|
+
|
|
35
|
+
Keyword Arguments:
|
|
36
|
+
eps_0 (float): Peak strain of concrete in compression. Default
|
|
37
|
+
value = -0.002.
|
|
38
|
+
eps_u (float): Ultimate strain of concrete in compression. Default
|
|
39
|
+
value = -0.0035.
|
|
40
|
+
n (float): Exponent for the pre-peak branch. Default value = 2.
|
|
41
|
+
name (str): A name for the constitutive law.
|
|
42
|
+
"""
|
|
43
|
+
name = name if name is not None else 'ParabolaRectangleLaw'
|
|
44
|
+
super().__init__(name=name)
|
|
45
|
+
self._fc = -abs(fc)
|
|
46
|
+
self._eps_0 = -abs(eps_0)
|
|
47
|
+
self._eps_u = -abs(eps_u)
|
|
48
|
+
self._n = n
|
|
49
|
+
|
|
50
|
+
def get_stress(
|
|
51
|
+
self, eps: t.Union[float, ArrayLike]
|
|
52
|
+
) -> t.Union[float, ArrayLike]:
|
|
53
|
+
"""Return the stress given strain."""
|
|
54
|
+
eps = eps if np.isscalar(eps) else np.atleast_1d(eps)
|
|
55
|
+
# Preprocess eps array in order
|
|
56
|
+
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
57
|
+
# Compute stress
|
|
58
|
+
# If it is a scalar
|
|
59
|
+
if np.isscalar(eps):
|
|
60
|
+
sig = 0
|
|
61
|
+
if self._eps_0 <= eps <= 0:
|
|
62
|
+
sig = self._fc * (1 - (1 - eps / self._eps_0) ** self._n)
|
|
63
|
+
if self._eps_u <= eps < self._eps_0:
|
|
64
|
+
sig = self._fc
|
|
65
|
+
return sig
|
|
66
|
+
# If it is an array
|
|
67
|
+
sig = np.zeros_like(eps)
|
|
68
|
+
# Parabolic branch
|
|
69
|
+
sig[(eps <= 0) & (eps >= self._eps_0)] = self._fc * (
|
|
70
|
+
1
|
|
71
|
+
- (1 - (eps[(eps <= 0) & (eps >= self._eps_0)] / self._eps_0))
|
|
72
|
+
** self._n
|
|
73
|
+
)
|
|
74
|
+
# Rectangle branch
|
|
75
|
+
sig[eps < self._eps_0] = self._fc
|
|
76
|
+
# Zero elsewhere
|
|
77
|
+
sig[eps < self._eps_u] = 0
|
|
78
|
+
sig[eps > 0] = 0
|
|
79
|
+
return sig
|
|
80
|
+
|
|
81
|
+
def get_tangent(
|
|
82
|
+
self, eps: t.Union[float, ArrayLike]
|
|
83
|
+
) -> t.Union[float, ArrayLike]:
|
|
84
|
+
"""Return the tangent given strain."""
|
|
85
|
+
eps = eps if np.isscalar(eps) else np.atleast_1d(eps)
|
|
86
|
+
# If it is a scalar
|
|
87
|
+
if np.isscalar(eps):
|
|
88
|
+
tangent = 0
|
|
89
|
+
if self._eps_0 <= eps <= 0:
|
|
90
|
+
tangent = (
|
|
91
|
+
self._n
|
|
92
|
+
* self._fc
|
|
93
|
+
/ self._eps_0
|
|
94
|
+
* (1 - (eps / self._eps_0)) ** (self._n - 1)
|
|
95
|
+
)
|
|
96
|
+
return tangent
|
|
97
|
+
# If it is an array
|
|
98
|
+
# parabolic branch
|
|
99
|
+
tangent = np.zeros_like(eps)
|
|
100
|
+
tangent[(eps <= 0) & (eps >= self._eps_0)] = (
|
|
101
|
+
self._n
|
|
102
|
+
* self._fc
|
|
103
|
+
/ self._eps_0
|
|
104
|
+
* (1 - (eps[(eps <= 0) & (eps >= self._eps_0)] / self._eps_0))
|
|
105
|
+
** (self._n - 1)
|
|
106
|
+
)
|
|
107
|
+
# Elsewhere tangent is zero
|
|
108
|
+
tangent[eps < self._eps_0] = 0.0
|
|
109
|
+
tangent[eps > 0] = 0.0
|
|
110
|
+
return tangent
|
|
111
|
+
|
|
112
|
+
def __marin__(
|
|
113
|
+
self, strain: t.Tuple[float, float]
|
|
114
|
+
) -> t.Tuple[t.List[float], t.List[float]]:
|
|
115
|
+
"""Returns coefficients and strain limits for Marin integration in a
|
|
116
|
+
simply formatted way.
|
|
117
|
+
|
|
118
|
+
Arguments:
|
|
119
|
+
strain (float, float): Tuple defining the strain profile: eps =
|
|
120
|
+
strain[0] + strain[1]*y.
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
[(0, -0.002), (-0.002, -0.003)]
|
|
124
|
+
[(a0, a1, a2), (a0)]
|
|
125
|
+
"""
|
|
126
|
+
if self._n != 2:
|
|
127
|
+
# The constitutive law is not writtable as a polynomial,
|
|
128
|
+
# Call the generic distretizing method
|
|
129
|
+
return super().__marin__(strain=strain)
|
|
130
|
+
|
|
131
|
+
strains = []
|
|
132
|
+
coeff = []
|
|
133
|
+
if strain[1] == 0:
|
|
134
|
+
# Uniform strain equal to strain[0]
|
|
135
|
+
# understand in which branch are we
|
|
136
|
+
strain[0] = self.preprocess_strains_with_limits(strain[0])
|
|
137
|
+
if strain[0] > 0:
|
|
138
|
+
# We are in tensile branch
|
|
139
|
+
strains = None
|
|
140
|
+
coeff.append((0.0,))
|
|
141
|
+
elif strain[0] > self._eps_0:
|
|
142
|
+
# We are in the parabolic branch
|
|
143
|
+
strains = None
|
|
144
|
+
a0 = (
|
|
145
|
+
2
|
|
146
|
+
* self._fc
|
|
147
|
+
* strain[0]
|
|
148
|
+
/ self._eps_0
|
|
149
|
+
* (1 - 0.5 * (strain[0] / self._eps_0))
|
|
150
|
+
)
|
|
151
|
+
a1 = (
|
|
152
|
+
2
|
|
153
|
+
* self._fc
|
|
154
|
+
/ self._eps_0
|
|
155
|
+
* strain[1]
|
|
156
|
+
* (1 - strain[0] / self._eps_0)
|
|
157
|
+
)
|
|
158
|
+
coeff.append((a0, a1, 0.0))
|
|
159
|
+
elif strain[0] >= self._eps_u:
|
|
160
|
+
# We are in the constant branch
|
|
161
|
+
strains = None
|
|
162
|
+
coeff.append((self._fc,))
|
|
163
|
+
else:
|
|
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
|
+
# Parabolic part
|
|
170
|
+
strains.append((self._eps_0, 0))
|
|
171
|
+
a0 = (
|
|
172
|
+
2
|
|
173
|
+
* self._fc
|
|
174
|
+
* strain[0]
|
|
175
|
+
/ self._eps_0
|
|
176
|
+
* (1 - 0.5 * (strain[0] / self._eps_0))
|
|
177
|
+
)
|
|
178
|
+
a1 = (
|
|
179
|
+
2
|
|
180
|
+
* self._fc
|
|
181
|
+
/ self._eps_0
|
|
182
|
+
* strain[1]
|
|
183
|
+
* (1 - strain[0] / self._eps_0)
|
|
184
|
+
)
|
|
185
|
+
a2 = -self._fc * strain[1] ** 2 / self._eps_0**2
|
|
186
|
+
coeff.append((a0, a1, a2))
|
|
187
|
+
# Constant part
|
|
188
|
+
strains.append((self._eps_u, self._eps_0))
|
|
189
|
+
coeff.append((self._fc,))
|
|
190
|
+
return strains, coeff
|
|
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
|
+
|
|
249
|
+
def get_ultimate_strain(
|
|
250
|
+
self, yielding: bool = False
|
|
251
|
+
) -> t.Tuple[float, float]:
|
|
252
|
+
"""Return the ultimate strain (negative and positive)."""
|
|
253
|
+
if yielding:
|
|
254
|
+
return (self._eps_0, 100)
|
|
255
|
+
return (self._eps_u, 100)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Popovics 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 Popovics(ConstitutiveLaw):
|
|
14
|
+
"""Class for Popovics-Mander constitutive law.
|
|
15
|
+
|
|
16
|
+
The stresses and strains are assumed negative in compression and positive
|
|
17
|
+
in tension.
|
|
18
|
+
|
|
19
|
+
If the relation Ec = 5000 * sqrt(fc) is used for elastic modulus, the
|
|
20
|
+
constitutive law is identical to the one proposed by Mander et al. (1988).
|
|
21
|
+
|
|
22
|
+
References:
|
|
23
|
+
Popovics, S., 1973, “A Numerical Approach to the Complete Stress-Strain
|
|
24
|
+
Curve of Concrete”, Cement and Concrete Research, 3(4), 583-599.
|
|
25
|
+
|
|
26
|
+
Mander, J.B., Priestley, M.J.N., Park, R., 1988, "Theoretical Stress-Strain
|
|
27
|
+
Model for Confined Concrete", Journal of Structural Engineering, 114(8),
|
|
28
|
+
1804-1826.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__materials__: t.Tuple[str] = ('concrete',)
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
fc: float,
|
|
36
|
+
eps_c: float = -0.002,
|
|
37
|
+
eps_cu: float = -0.0035,
|
|
38
|
+
Ec: t.Optional[float] = None,
|
|
39
|
+
name: t.Optional[str] = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Initialize a Popovics Material.
|
|
42
|
+
|
|
43
|
+
Arguments:
|
|
44
|
+
fc (float): the strength of concrete in compression
|
|
45
|
+
|
|
46
|
+
Keyword Arguments:
|
|
47
|
+
eps_c (float): Peak strain of concrete in compression. Default
|
|
48
|
+
value = -0.002.
|
|
49
|
+
eps_cu (float): Ultimate strain of concrete in compression. Default
|
|
50
|
+
value = -0.0035.
|
|
51
|
+
E (optional float): Elastic modulus of concrete. If None, the
|
|
52
|
+
equation Ec = 5000 * fc**0.5 proposed by Mander et al. (1988)
|
|
53
|
+
is adopted (fc in MPa). Default value = None.
|
|
54
|
+
name (str): A name for the constitutive law.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
ValueError: If E is less or equal to 0.
|
|
58
|
+
|
|
59
|
+
Note:
|
|
60
|
+
If positive values are input for fc, eps_c and eps_cu are input,
|
|
61
|
+
they will be assumed negative.
|
|
62
|
+
"""
|
|
63
|
+
name = name if name is not None else 'PopovicsLaw'
|
|
64
|
+
super().__init__(name=name)
|
|
65
|
+
self._fc = -abs(fc)
|
|
66
|
+
self._eps_c = -abs(eps_c)
|
|
67
|
+
self._eps_cu = -abs(eps_cu)
|
|
68
|
+
if Ec is None:
|
|
69
|
+
# fc in MPa, relation of Mander et al. (1988)
|
|
70
|
+
Ec = 5000 * abs(fc) ** 0.5
|
|
71
|
+
if Ec <= 0:
|
|
72
|
+
raise ValueError('Elastic modulus must be a positive number.')
|
|
73
|
+
E_sec = self._fc / self._eps_c
|
|
74
|
+
self._n = Ec / (Ec - E_sec)
|
|
75
|
+
|
|
76
|
+
def get_stress(
|
|
77
|
+
self, eps: t.Union[float, ArrayLike]
|
|
78
|
+
) -> t.Union[float, ArrayLike]:
|
|
79
|
+
"""Return the stress given the strain."""
|
|
80
|
+
eps = eps if np.isscalar(eps) else np.atleast_1d(eps)
|
|
81
|
+
# Preprocess eps array in order
|
|
82
|
+
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
83
|
+
# Compute stress
|
|
84
|
+
# Compression branch
|
|
85
|
+
eta = eps / self._eps_c
|
|
86
|
+
|
|
87
|
+
sig = self._fc * eta * self._n / (self._n - 1 + eta**self._n)
|
|
88
|
+
|
|
89
|
+
# Elsewhere stress is 0.0
|
|
90
|
+
if np.isscalar(eps):
|
|
91
|
+
if eps < self._eps_cu or eps > 0:
|
|
92
|
+
return 0.0
|
|
93
|
+
else:
|
|
94
|
+
sig[eps < self._eps_cu] = 0.0
|
|
95
|
+
sig[eps > 0] = 0.0
|
|
96
|
+
|
|
97
|
+
return sig
|
|
98
|
+
|
|
99
|
+
def get_tangent(
|
|
100
|
+
self, eps: t.Union[float, ArrayLike]
|
|
101
|
+
) -> t.Union[float, ArrayLike]:
|
|
102
|
+
"""Return the tangent given strain."""
|
|
103
|
+
eps = eps if np.isscalar(eps) else np.atleast_1d(eps)
|
|
104
|
+
# Preprocess eps array in order
|
|
105
|
+
eps = self.preprocess_strains_with_limits(eps=eps)
|
|
106
|
+
# Compression branch
|
|
107
|
+
eta = eps / self._eps_c
|
|
108
|
+
|
|
109
|
+
tangent = (
|
|
110
|
+
(1 - eta**self._n)
|
|
111
|
+
/ (self._n - 1 + eta**self._n) ** 2
|
|
112
|
+
* self._n
|
|
113
|
+
* (self._n - 1)
|
|
114
|
+
* self._fc
|
|
115
|
+
/ self._eps_c
|
|
116
|
+
)
|
|
117
|
+
# Elsewhere tangent is zero
|
|
118
|
+
if np.isscalar(eps):
|
|
119
|
+
if eps < self._eps_cu or eps > 0:
|
|
120
|
+
return 0
|
|
121
|
+
else:
|
|
122
|
+
tangent[eps < self._eps_cu] = 0.0
|
|
123
|
+
tangent[eps > 0] = 0.0
|
|
124
|
+
|
|
125
|
+
return tangent
|
|
126
|
+
|
|
127
|
+
def get_ultimate_strain(
|
|
128
|
+
self, yielding: bool = False
|
|
129
|
+
) -> t.Tuple[float, float]:
|
|
130
|
+
"""Return the ultimate strain (negative and positive)."""
|
|
131
|
+
if yielding:
|
|
132
|
+
return (self._eps_c, 100)
|
|
133
|
+
return (self._eps_cu, 100)
|