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.

@@ -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)