elasticipy 2.8.10__py3-none-any.whl → 3.0.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.
Elasticipy/Plasticity.py CHANGED
@@ -1,9 +1,73 @@
1
1
  import numpy as np
2
+ from Elasticipy.StressStrainTensors import StrainTensor, StressTensor
2
3
 
3
4
 
5
+ class IsotropicHardening:
6
+ """
7
+ Template class for isotropic hardening plasticity models
8
+ """
9
+ def __init__(self, criterion='von Mises'):
10
+ """
11
+ Create an instance of a plastic model, assuming isotropic hardening
12
+
13
+ Parameters
14
+ ----------
15
+ criterion : str or PlasticityCriterion
16
+ Plasticity criterion to use. Can be 'von Mises', 'Tresca' or 'J2'. J2 is the same as von Mises.
17
+ """
18
+ if isinstance(criterion, str):
19
+ criterion = criterion.lower()
20
+ if criterion in ('von mises', 'mises', 'vonmises', 'j2'):
21
+ self.criterion = VonMisesPlasticity
22
+ elif criterion == 'tresca':
23
+ self.criterion = TrescaPlasticity
24
+ else:
25
+ raise ValueError('The criterion can be "Tresca", "von Mises" or "J2".')
26
+ else:
27
+ self.criterion = criterion
28
+ self.plastic_strain = 0.0
29
+
30
+ def flow_stress(self, strain, **kwargs):
31
+ pass
32
+
33
+ def apply_strain(self, strain, **kwargs):
34
+ """
35
+ Apply strain to the current JC model.
36
+
37
+ This function updates the internal variable to store hardening state.
38
+
39
+ Parameters
40
+ ----------
41
+ strain : float or StrainTensor
42
+ kwargs : dict
43
+ Keyword arguments passed to flow_stress()
44
+
45
+ Returns
46
+ -------
47
+ float
48
+ Associated flow stress (positive)
4
49
 
5
- class JohnsonCook:
6
- def __init__(self, A, B, n, C=None, eps_dot_ref=1.0, m=None, T0=25, Tm=None):
50
+ See Also
51
+ --------
52
+ flow_stress : compute the flow stress, given a cumulative equivalent strain
53
+ """
54
+ if isinstance(strain, float):
55
+ self.plastic_strain += np.abs(strain)
56
+ elif isinstance(strain, StrainTensor):
57
+ self.plastic_strain += strain.eq_strain()
58
+ else:
59
+ raise ValueError('The applied strain must be float of StrainTensor')
60
+ return self.flow_stress(self.plastic_strain, **kwargs)
61
+
62
+ def compute_strain_increment(self, stress, **kwargs):
63
+ pass
64
+
65
+ def reset_strain(self):
66
+ self.plastic_strain = 0.0
67
+
68
+
69
+ class JohnsonCook(IsotropicHardening):
70
+ def __init__(self, A, B, n, C=None, eps_dot_ref=1.0, m=None, T0=25, Tm=None, criterion='von Mises'):
7
71
  """
8
72
  Constructor for a Jonhson-Cook (JC) model.
9
73
 
@@ -28,6 +92,8 @@ class JohnsonCook:
28
92
  Reference temperature
29
93
  Tm : float, optional
30
94
  Melting temperature (at which the flow stress is zero)
95
+ criterion : str or PlasticityCriterion, optional
96
+ Plasticity criterion to use. It can be 'von Mises' or 'Tresca'.
31
97
 
32
98
  Notes
33
99
  -----
@@ -50,6 +116,7 @@ class JohnsonCook:
50
116
  1 & \\text{otherwise}
51
117
  \\end{cases}
52
118
  """
119
+ super().__init__(criterion=criterion)
53
120
  self.A = A
54
121
  self.B = B
55
122
  self.C = C
@@ -74,7 +141,7 @@ class JohnsonCook:
74
141
  Temperature. If float, the temperature is supposed to be homogeneous for every value of eps_p.
75
142
  Returns
76
143
  -------
77
- numpy.ndarray
144
+ float or numpy.ndarray
78
145
  Flow stress
79
146
  """
80
147
  eps_p = np.asarray(eps_p)
@@ -97,29 +164,169 @@ class JohnsonCook:
97
164
  return stress
98
165
 
99
166
 
100
- def compute_strain(self, stress, T=None):
167
+
168
+ def compute_strain_increment(self, stress, T=None, apply_strain=True, criterion='von Mises'):
101
169
  """
102
- Given the equivalent stress, compute the strain
170
+ Given the equivalent stress, compute the strain increment with respect to the normality rule.
103
171
 
104
172
  Parameters
105
173
  ----------
106
- stress : float or numpy.ndarray
107
- Equivalent stress tom compute the stress from
108
- T : float or list or tuple or numpy.ndarray
174
+ stress : float or StressTensor
175
+ Equivalent stress to compute the stress from, or full stress tensor.
176
+ T : float
109
177
  Temperature
178
+ apply_strain : bool, optional
179
+ If true, the JC model will be updated to account for the applied strain (hardening)
180
+ criterion : str, optional
181
+ Plasticity criterion to consider to compute the equivalent stress and apply the normality rule.
182
+ It can be 'von Mises', 'Tresca' or 'J2'. 'J2' is equivalent to 'von Mises'.
110
183
 
111
184
  Returns
112
185
  -------
113
- numpy.ndarray
114
- Equivalent strain
186
+ StrainTensor or float
187
+ Increment of plastic strain. If the input stress is float, only the magnitude of the increment will be
188
+ returned (float value). If the stress is of type StressTensor, the returned value will be a full
189
+ StrainTensor.
190
+
191
+ See Also
192
+ --------
193
+ apply_strain : apply strain to the JC model and updates its hardening value
115
194
  """
116
- stress = np.asarray(stress)
195
+ if isinstance(stress, StressTensor):
196
+ eq_stress = self.criterion.eq_stress(stress)
197
+ else:
198
+ eq_stress = stress
117
199
  if T is None:
118
- theta_m=0.0
200
+ if eq_stress > self.A:
201
+ k = eq_stress - self.A
202
+ total_strain = (1 / self.B * k) ** (1 / self.n)
203
+ strain_increment = np.max((total_strain - self.plastic_strain, 0))
204
+ else:
205
+ strain_increment = 0.0
119
206
  else:
120
207
  if self.T0 is None or self.Tm is None or self.m is None:
121
208
  raise ValueError('T0, Tm and m must be defined for using a temperature-dependent model')
122
209
  else:
123
- theta = (T - self.T0) / (self.Tm - self.T0)
124
- theta_m = np.clip(theta**self.m, None, 1.0)
125
- return (1/self.B * ( stress / (1 - theta_m) - self.A))**(1/self.n)
210
+ if T >= self.Tm:
211
+ strain_increment = np.inf
212
+ else:
213
+ theta = (T - self.T0) / (self.Tm - self.T0)
214
+ theta_m = theta**self.m
215
+ k = (eq_stress / (1 - theta_m) - self.A)
216
+ if k<0:
217
+ strain_increment = 0.0
218
+ else:
219
+ total_strain = (1/self.B * k)**(1/self.n)
220
+ strain_increment = np.max((total_strain - self.plastic_strain, 0))
221
+ if apply_strain:
222
+ self.apply_strain(strain_increment)
223
+
224
+ if isinstance(stress, StressTensor):
225
+ n = self.criterion.normal(stress)
226
+ return n * strain_increment
227
+ else:
228
+ return strain_increment
229
+
230
+ def reset_strain(self):
231
+ """
232
+ Reinitialize the plastic strain to 0
233
+ """
234
+ self.plastic_strain = 0.0
235
+
236
+
237
+ class PlasticityCriterion:
238
+ @staticmethod
239
+ def eq_stress(stress, **kwargs):
240
+ """
241
+ Return the equivalent stress, with respect to the plasticity criterion.
242
+
243
+ Parameters
244
+ ----------
245
+ stress : StressTensor
246
+ Stress to compute the equivalent stress from
247
+ kwargs : dict
248
+ keyword arguments passed to the function
249
+ Returns
250
+ -------
251
+ float or numpy.ndarray
252
+ """
253
+ pass
254
+
255
+ def normal(self, stress, **kwargs):
256
+ """
257
+ Apply the normality rule
258
+
259
+ Parameters
260
+ ----------
261
+ stress : StressTensor
262
+ Stress tensor to apply the normality rule
263
+ kwargs : dict
264
+ Keyword arguments passed to the function
265
+
266
+ Returns
267
+ -------
268
+ StrainTensor
269
+ Normalized direction of plastic flow
270
+ """
271
+ pass
272
+
273
+ class VonMisesPlasticity(PlasticityCriterion):
274
+ @staticmethod
275
+ def eq_stress(stress, **kwargs):
276
+ return stress.vonMises()
277
+
278
+ @staticmethod
279
+ def normal(stress, **kwargs):
280
+ eq_stress = stress.vonMises()
281
+ dev_stress = stress.deviatoric_part()
282
+ gradient_tensor = dev_stress / eq_stress
283
+ return StrainTensor(3 / 2 * gradient_tensor.matrix)
284
+
285
+ class TrescaPlasticity(PlasticityCriterion):
286
+ @staticmethod
287
+ def eq_stress(stress, **kwargs):
288
+ return stress.Tresca()
289
+
290
+ @staticmethod
291
+ def normal(stress, **kwargs):
292
+ vals, dirs = stress.eig()
293
+ u1 = dirs[..., 0]
294
+ u3 = dirs[..., 2]
295
+ s1 = vals[..., 0]
296
+ s2 = vals[..., 1]
297
+ s3 = vals[..., 2]
298
+ a = np.einsum('...i,...j->...ij', u1, u1)
299
+ b = np.einsum('...i,...j->...ij', u3, u3)
300
+ normal = a - b
301
+ singular_points = np.logical_or(s2 == s1, s2 == s3)
302
+ normal[singular_points] = VonMisesPlasticity().normal(stress[singular_points]).matrix
303
+ normal[np.logical_and(s2 == s1, s2 == s3)] = 0.0
304
+ strain = StrainTensor(normal)
305
+ return strain / strain.eq_strain()
306
+
307
+ class DruckerPrager(PlasticityCriterion):
308
+ def __init__(self, alpha):
309
+ """
310
+ Create a Drucker-Prager (DG) plasticity criterion.
311
+
312
+ Parameters
313
+ ----------
314
+ alpha : float
315
+ Pressure dependence parameters (see notes for details)
316
+
317
+ Notes
318
+ -----
319
+ The pressure-dependent DG plasticity criterion assumes that the equivalent stress is defined as:
320
+
321
+
322
+ """
323
+ self.alpha = alpha
324
+
325
+ def eq_stress(self, stress, **kwargs):
326
+ return (stress.J2**0.5 + self.alpha * stress.I1) / (1/3**0.5 + self.alpha)
327
+
328
+ def normal(self, stress, **kwargs):
329
+ J2 = stress.J2
330
+ gradient = stress.deviatoric_part() / (2 * J2**0.5) + self.alpha * StressTensor.eye(stress.shape)
331
+ strain = StrainTensor(gradient.matrix)
332
+ return strain / strain.eq_strain()