structuralcodes 0.1.0__py3-none-any.whl → 0.2.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/ec2_2004/shear.py +5 -1
- structuralcodes/codes/mc2010/_concrete_creep_and_shrinkage.py +3 -1
- structuralcodes/core/base.py +16 -4
- structuralcodes/geometry/_geometry.py +4 -2
- structuralcodes/geometry/_reinforcement.py +14 -2
- structuralcodes/materials/constitutive_laws/__init__.py +84 -0
- structuralcodes/materials/constitutive_laws/_bilinearcompression.py +138 -0
- structuralcodes/materials/constitutive_laws/_elastic.py +114 -0
- structuralcodes/materials/constitutive_laws/_elasticplastic.py +172 -0
- structuralcodes/materials/constitutive_laws/_parabolarectangle.py +198 -0
- structuralcodes/materials/constitutive_laws/_popovics.py +133 -0
- structuralcodes/materials/constitutive_laws/_sargin.py +115 -0
- structuralcodes/materials/constitutive_laws/_userdefined.py +218 -0
- structuralcodes/sections/_generic.py +39 -9
- structuralcodes/sections/section_integrators/_fiber_integrator.py +1 -3
- structuralcodes/sections/section_integrators/_marin_integrator.py +1 -1
- {structuralcodes-0.1.0.dist-info → structuralcodes-0.2.0.dist-info}/METADATA +1 -1
- {structuralcodes-0.1.0.dist-info → structuralcodes-0.2.0.dist-info}/RECORD +22 -15
- structuralcodes/codes/ec2_2004/annex_b_shrink_and_creep.py +0 -257
- structuralcodes/materials/constitutive_laws.py +0 -981
- {structuralcodes-0.1.0.dist-info → structuralcodes-0.2.0.dist-info}/WHEEL +0 -0
|
@@ -272,7 +272,7 @@ class PointGeometry(Geometry):
|
|
|
272
272
|
else:
|
|
273
273
|
# new_material not provided, assume elastic material with same
|
|
274
274
|
# elastic modulus
|
|
275
|
-
new_material = Elastic(E=geo.material.get_tangent(eps=0)
|
|
275
|
+
new_material = Elastic(E=geo.material.get_tangent(eps=0))
|
|
276
276
|
|
|
277
277
|
return PointGeometry(
|
|
278
278
|
point=geo._point,
|
|
@@ -557,6 +557,7 @@ class SurfaceGeometry:
|
|
|
557
557
|
poly=affinity.translate(self.polygon, dx, dy),
|
|
558
558
|
material=self.material,
|
|
559
559
|
density=self._density,
|
|
560
|
+
concrete=self.concrete
|
|
560
561
|
)
|
|
561
562
|
|
|
562
563
|
def rotate(
|
|
@@ -584,6 +585,7 @@ class SurfaceGeometry:
|
|
|
584
585
|
),
|
|
585
586
|
material=self.material,
|
|
586
587
|
density=self._density,
|
|
588
|
+
concrete=self.concrete,
|
|
587
589
|
)
|
|
588
590
|
|
|
589
591
|
@staticmethod
|
|
@@ -622,7 +624,7 @@ class SurfaceGeometry:
|
|
|
622
624
|
else:
|
|
623
625
|
# new_material not provided, assume elastic material with same
|
|
624
626
|
# elastic modulus
|
|
625
|
-
new_material = Elastic(E=geo.material.get_tangent(eps=0)
|
|
627
|
+
new_material = Elastic(E=geo.material.get_tangent(eps=0))
|
|
626
628
|
|
|
627
629
|
return SurfaceGeometry(
|
|
628
630
|
poly=geo.polygon, material=new_material, density=geo._density
|
|
@@ -18,6 +18,7 @@ def add_reinforcement(
|
|
|
18
18
|
coords: t.Tuple[float, float],
|
|
19
19
|
diameter: float,
|
|
20
20
|
material: t.Union[Material, ConstitutiveLaw],
|
|
21
|
+
group_label: t.Optional[str] = None,
|
|
21
22
|
) -> CompoundGeometry:
|
|
22
23
|
"""Add a single bar given coordinate.
|
|
23
24
|
|
|
@@ -28,12 +29,16 @@ def add_reinforcement(
|
|
|
28
29
|
diameter (float): The diameter of the reinforcement.
|
|
29
30
|
material (Union(Material, ConstitutiveLaw)): A material or a
|
|
30
31
|
constitutive law for the behavior of the reinforcement.
|
|
32
|
+
group_label (Optional(str)): A label for grouping several objects
|
|
33
|
+
(default is None).
|
|
31
34
|
|
|
32
35
|
Returns:
|
|
33
36
|
CompoundGeometry: A compound geometry with the original geometry and
|
|
34
37
|
the reinforcement.
|
|
35
38
|
"""
|
|
36
|
-
bar = PointGeometry(
|
|
39
|
+
bar = PointGeometry(
|
|
40
|
+
Point(coords), diameter, material, group_label=group_label
|
|
41
|
+
)
|
|
37
42
|
return geo + bar
|
|
38
43
|
|
|
39
44
|
|
|
@@ -47,6 +52,7 @@ def add_reinforcement_line(
|
|
|
47
52
|
s: float = 0.0,
|
|
48
53
|
first: bool = True,
|
|
49
54
|
last: bool = True,
|
|
55
|
+
group_label: t.Optional[str] = None,
|
|
50
56
|
) -> CompoundGeometry:
|
|
51
57
|
"""Adds a set of bars distributed in a line.
|
|
52
58
|
|
|
@@ -66,6 +72,8 @@ def add_reinforcement_line(
|
|
|
66
72
|
True).
|
|
67
73
|
last (bool): Boolean indicating if placing the last bar (default =
|
|
68
74
|
True).
|
|
75
|
+
group_label (Optional(str)): A label for grouping several objects
|
|
76
|
+
(default is None).
|
|
69
77
|
|
|
70
78
|
Note:
|
|
71
79
|
At least n or s should be greater than zero.
|
|
@@ -110,6 +118,10 @@ def add_reinforcement_line(
|
|
|
110
118
|
continue
|
|
111
119
|
coords = p1 + v * s * i
|
|
112
120
|
geo = add_reinforcement(
|
|
113
|
-
geo,
|
|
121
|
+
geo,
|
|
122
|
+
(coords[0], coords[1]),
|
|
123
|
+
diameter,
|
|
124
|
+
material,
|
|
125
|
+
group_label=group_label,
|
|
114
126
|
)
|
|
115
127
|
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,138 @@
|
|
|
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_0:
|
|
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 get_ultimate_strain(
|
|
133
|
+
self, yielding: bool = False
|
|
134
|
+
) -> t.Tuple[float, float]:
|
|
135
|
+
"""Return the ultimate strain (negative and positive)."""
|
|
136
|
+
if yielding:
|
|
137
|
+
return (self._eps_c, 100)
|
|
138
|
+
return (self._eps_cu, 100)
|
|
@@ -0,0 +1,114 @@
|
|
|
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 get_ultimate_strain(self, **kwargs) -> t.Tuple[float, float]:
|
|
73
|
+
"""Return the ultimate strain (negative and positive)."""
|
|
74
|
+
# There is no real strain limit, so set it to very large values
|
|
75
|
+
# unlesse specified by the user differently
|
|
76
|
+
del kwargs
|
|
77
|
+
return self._eps_su or (-100, 100)
|
|
78
|
+
|
|
79
|
+
def set_ultimate_strain(
|
|
80
|
+
self, eps_su=t.Union[float, t.Tuple[float, float]]
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Set ultimate strains for Elastic Material if needed.
|
|
83
|
+
|
|
84
|
+
Arguments:
|
|
85
|
+
eps_su (float or (float, float)): Defining ultimate strain. If a
|
|
86
|
+
single value is provided the same is adopted for both negative
|
|
87
|
+
and positive strains. If a tuple is provided, it should be
|
|
88
|
+
given as (negative, positive).
|
|
89
|
+
"""
|
|
90
|
+
if isinstance(eps_su, float):
|
|
91
|
+
self._eps_su = (-abs(eps_su), abs(eps_su))
|
|
92
|
+
elif isinstance(eps_su, tuple):
|
|
93
|
+
if len(eps_su) < 2:
|
|
94
|
+
raise ValueError(
|
|
95
|
+
'Two values need to be provided when setting the tuple'
|
|
96
|
+
)
|
|
97
|
+
eps_su_n = eps_su[0]
|
|
98
|
+
eps_su_p = eps_su[1]
|
|
99
|
+
if eps_su_p < eps_su_n:
|
|
100
|
+
eps_su_p, eps_su_n = eps_su_n, eps_su_p
|
|
101
|
+
if eps_su_p < 0:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
'Positive ultimate strain should be non-negative'
|
|
104
|
+
)
|
|
105
|
+
if eps_su_n > 0:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
'Negative utimate strain should be non-positive'
|
|
108
|
+
)
|
|
109
|
+
self._eps_su = (eps_su_n, eps_su_p)
|
|
110
|
+
else:
|
|
111
|
+
raise ValueError(
|
|
112
|
+
'set_ultimate_strain requires a single value or a tuple \
|
|
113
|
+
with two values'
|
|
114
|
+
)
|
|
@@ -0,0 +1,172 @@
|
|
|
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 get_ultimate_strain(
|
|
164
|
+
self, yielding: bool = False
|
|
165
|
+
) -> t.Tuple[float, float]:
|
|
166
|
+
"""Return the ultimate strain (negative and positive)."""
|
|
167
|
+
if yielding:
|
|
168
|
+
return (-self._eps_sy, self._eps_sy)
|
|
169
|
+
# If not specified eps
|
|
170
|
+
if self._eps_su is None:
|
|
171
|
+
return (-self._eps_sy * 2, self._eps_sy * 2)
|
|
172
|
+
return (-self._eps_su, self._eps_su)
|