structuralcodes 0.1.1__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/core/base.py +16 -4
- structuralcodes/geometry/_geometry.py +4 -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 +13 -3
- structuralcodes/sections/section_integrators/_fiber_integrator.py +1 -3
- structuralcodes/sections/section_integrators/_marin_integrator.py +1 -1
- {structuralcodes-0.1.1.dist-info → structuralcodes-0.2.0.dist-info}/METADATA +1 -1
- {structuralcodes-0.1.1.dist-info → structuralcodes-0.2.0.dist-info}/RECORD +19 -12
- structuralcodes/codes/ec2_2004/annex_b_shrink_and_creep.py +0 -257
- structuralcodes/materials/constitutive_laws.py +0 -981
- {structuralcodes-0.1.1.dist-info → structuralcodes-0.2.0.dist-info}/WHEEL +0 -0
|
@@ -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)
|
|
@@ -0,0 +1,198 @@
|
|
|
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 get_ultimate_strain(
|
|
193
|
+
self, yielding: bool = False
|
|
194
|
+
) -> t.Tuple[float, float]:
|
|
195
|
+
"""Return the ultimate strain (negative and positive)."""
|
|
196
|
+
if yielding:
|
|
197
|
+
return (self._eps_0, 100)
|
|
198
|
+
return (self._eps_u, 100)
|