elasticipy 2.8.9__py3-none-any.whl → 2.9.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,74 @@
1
1
  import numpy as np
2
+ from Elasticipy.StressStrainTensors import StrainTensor, StressTensor
2
3
 
4
+ class IsotropicHardening:
5
+ """
6
+ Template class for isotropic hardening plasticity models
7
+ """
8
+ def __init__(self, criterion='von Mises'):
9
+ """
10
+ Create an instance of a plastic model, assuming isotropic hardening
11
+
12
+ Parameters
13
+ ----------
14
+ criterion : str, optional
15
+ Plasticity criterion to use. Can be 'von Mises', 'Tresca' or 'J2'. J2 is the same as von Mises.
16
+ """
17
+ criterion = criterion.lower()
18
+ if criterion in ('von mises', 'mises', 'vonmises', 'j2'):
19
+ criterion = 'j2'
20
+ elif criterion != 'tresca':
21
+ raise ValueError('The criterion can be "Tresca", "von Mises" or "J2".')
22
+ self.criterion = criterion.lower()
23
+ self.plastic_strain = 0.0
3
24
 
25
+ def flow_stress(self, strain, **kwargs):
26
+ pass
4
27
 
5
- class JohnsonCook:
6
- def __init__(self, A, B, n, C=None, eps_dot_ref=1.0, m=None, T0=25, Tm=None):
28
+ def apply_strain(self, strain, **kwargs):
29
+ """
30
+ Apply strain to the current JC model.
31
+
32
+ This function updates the internal variable to store hardening state.
33
+
34
+ Parameters
35
+ ----------
36
+ strain : float or StrainTensor
37
+ kwargs : dict
38
+ Keyword arguments passed to flow_stress()
39
+
40
+ Returns
41
+ -------
42
+ float
43
+ Associated flow stress (positive)
44
+
45
+ See Also
46
+ --------
47
+ flow_stress : compute the flow stress, given a cumulative equivalent strain
48
+ """
49
+ if isinstance(strain, float):
50
+ self.plastic_strain += np.abs(strain)
51
+ elif isinstance(strain, StrainTensor):
52
+ self.plastic_strain += strain.eq_strain()
53
+ else:
54
+ raise ValueError('The applied strain must be float of StrainTensor')
55
+ return self.flow_stress(self.plastic_strain, **kwargs)
56
+
57
+ def compute_strain_increment(self, stress, **kwargs):
58
+ pass
59
+
60
+ def reset_strain(self):
61
+ self.plastic_strain = 0.0
62
+
63
+ def eq_stress(self, stress):
64
+ if self.criterion == 'j2':
65
+ return stress.vonMises()
66
+ else:
67
+ return stress.Tresca()
68
+
69
+
70
+ class JohnsonCook(IsotropicHardening):
71
+ def __init__(self, A, B, n, C=None, eps_dot_ref=1.0, m=None, T0=25, Tm=None, criterion='von Mises'):
7
72
  """
8
73
  Constructor for a Jonhson-Cook (JC) model.
9
74
 
@@ -28,6 +93,8 @@ class JohnsonCook:
28
93
  Reference temperature
29
94
  Tm : float, optional
30
95
  Melting temperature (at which the flow stress is zero)
96
+ criterion : str, optional
97
+ Plasticity criterion to use. It can be 'von Mises' or 'Tresca'.
31
98
 
32
99
  Notes
33
100
  -----
@@ -50,6 +117,7 @@ class JohnsonCook:
50
117
  1 & \\text{otherwise}
51
118
  \\end{cases}
52
119
  """
120
+ super().__init__(criterion=criterion)
53
121
  self.A = A
54
122
  self.B = B
55
123
  self.C = C
@@ -74,7 +142,7 @@ class JohnsonCook:
74
142
  Temperature. If float, the temperature is supposed to be homogeneous for every value of eps_p.
75
143
  Returns
76
144
  -------
77
- numpy.ndarray
145
+ float or numpy.ndarray
78
146
  Flow stress
79
147
  """
80
148
  eps_p = np.asarray(eps_p)
@@ -97,29 +165,118 @@ class JohnsonCook:
97
165
  return stress
98
166
 
99
167
 
100
- def compute_strain(self, stress, T=None):
168
+
169
+ def compute_strain_increment(self, stress, T=None, apply_strain=True, criterion='von Mises'):
101
170
  """
102
- Given the equivalent stress, compute the strain
171
+ Given the equivalent stress, compute the strain increment with respect to the normality rule.
103
172
 
104
173
  Parameters
105
174
  ----------
106
- stress : float or numpy.ndarray
107
- Equivalent stress tom compute the stress from
108
- T : float or list or tuple or numpy.ndarray
175
+ stress : float or StressTensor
176
+ Equivalent stress to compute the stress from, or full stress tensor.
177
+ T : float
109
178
  Temperature
179
+ apply_strain : bool, optional
180
+ If true, the JC model will be updated to account for the applied strain (hardening)
181
+ criterion : str, optional
182
+ Plasticity criterion to consider to compute the equivalent stress and apply the normality rule.
183
+ It can be 'von Mises', 'Tresca' or 'J2'. 'J2' is equivalent to 'von Mises'.
110
184
 
111
185
  Returns
112
186
  -------
113
- numpy.ndarray
114
- Equivalent strain
187
+ StrainTensor or float
188
+ Increment of plastic strain. If the input stress is float, only the magnitude of the increment will be
189
+ returned (float value). If the stress is of type StressTensor, the returned value will be a full
190
+ StrainTensor.
191
+
192
+ See Also
193
+ --------
194
+ apply_strain : apply strain to the JC model and updates its hardening value
115
195
  """
116
- stress = np.asarray(stress)
196
+ if isinstance(stress, StressTensor):
197
+ eq_stress = self.eq_stress(stress)
198
+ else:
199
+ eq_stress = stress
117
200
  if T is None:
118
- theta_m=0.0
201
+ if eq_stress > self.A:
202
+ k = eq_stress - self.A
203
+ total_strain = (1 / self.B * k) ** (1 / self.n)
204
+ strain_increment = np.max((total_strain - self.plastic_strain, 0))
205
+ else:
206
+ strain_increment = 0.0
119
207
  else:
120
208
  if self.T0 is None or self.Tm is None or self.m is None:
121
209
  raise ValueError('T0, Tm and m must be defined for using a temperature-dependent model')
122
210
  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)
211
+ if T >= self.Tm:
212
+ strain_increment = np.inf
213
+ else:
214
+ theta = (T - self.T0) / (self.Tm - self.T0)
215
+ theta_m = theta**self.m
216
+ k = (eq_stress / (1 - theta_m) - self.A)
217
+ if k<0:
218
+ strain_increment = 0.0
219
+ else:
220
+ total_strain = (1/self.B * k)**(1/self.n)
221
+ strain_increment = np.max((total_strain - self.plastic_strain, 0))
222
+ if apply_strain:
223
+ self.apply_strain(strain_increment)
224
+
225
+ if isinstance(stress, StressTensor):
226
+ n = normality_rule(stress, criterion=criterion)
227
+ return n * strain_increment
228
+ else:
229
+ return strain_increment
230
+
231
+ def reset_strain(self):
232
+ """
233
+ Reinitialize the plastic strain to 0
234
+ """
235
+ self.plastic_strain = 0.0
236
+
237
+
238
+ def normality_rule(stress, criterion='von Mises'):
239
+ """
240
+ Apply the normality rule for plastic flow, given a yield criterion.
241
+
242
+ The stress can be a single tensor, or an array of tensors.
243
+
244
+ Parameters
245
+ ----------
246
+ stress : StressTensor
247
+ Stress tensor to apply the normality rule from
248
+ criterion : str, optional
249
+ Name of the criterion to use. Can be either 'von Mises' or 'Tresca'
250
+
251
+ Returns
252
+ -------
253
+ StrainTensor
254
+ If a single stress tensor is passed, the returned array will be of shape
255
+
256
+ Notes
257
+ -----
258
+ The singular points for the Tresca criterion are treated as the von Mises criterion, which is equivalent to the
259
+ average of the two adjacent normals of the domain.
260
+ """
261
+ if criterion.lower()=='von mises':
262
+ eq_stress = stress.vonMises()
263
+ dev_stress= stress.deviatoric_part()
264
+ gradient_tensor = dev_stress / eq_stress
265
+ return StrainTensor(3/2 * gradient_tensor.matrix)
266
+ elif criterion.lower()=='tresca':
267
+ vals, dirs = stress.eig()
268
+ u1 = dirs[...,0]
269
+ u3 = dirs[...,2]
270
+ s1 = vals[...,0]
271
+ s2 = vals[..., 1]
272
+ s3 = vals[...,2]
273
+ A = np.einsum('...i,...j->...ij',u1, u1)
274
+ B = np.einsum('...i,...j->...ij',u3, u3)
275
+ normal = A - B
276
+ singular_points = np.logical_or(s2==s1, s2==s3)
277
+ normal[singular_points] = normality_rule(stress[singular_points], criterion='von Mises').matrix
278
+ normal[np.logical_and(s2==s1, s2==s3)] = 0.0
279
+ strain = StrainTensor(normal)
280
+ return strain / strain.eq_strain()
281
+ else:
282
+ raise NotImplementedError('The normality rule is only implemented for von Mises (J2) and Tresca criteria.')
@@ -290,7 +290,7 @@ class SecondOrderTensor:
290
290
  @property
291
291
  def J2(self):
292
292
  """
293
- Second invariant of the deviatoric part of the stress tensor.
293
+ Second invariant of the deviatoric part of the tensor.
294
294
 
295
295
  Returns
296
296
  -------
@@ -302,7 +302,7 @@ class SecondOrderTensor:
302
302
  @property
303
303
  def J3(self):
304
304
  """
305
- Third invariant of the deviatoric part of the stress tensor.
305
+ Third invariant of the deviatoric part of the tensor.
306
306
 
307
307
  Returns
308
308
  -------
@@ -311,6 +311,48 @@ class SecondOrderTensor:
311
311
  """
312
312
  return self.deviatoric_part().I3
313
313
 
314
+ def Lode_angle(self, degrees=False):
315
+ """
316
+ Computes the Lode angle of the tensor.
317
+
318
+ The returned value is defined from the positive cosine (see Notes).
319
+
320
+ Parameters
321
+ ----------
322
+ degrees : bool, optional
323
+ Whether to return the angle in degrees or not
324
+
325
+ Returns
326
+ -------
327
+ float or numpy.ndarray
328
+
329
+ See Also
330
+ --------
331
+ J2 : Second invariant of the deviatoric part
332
+ J3 : Third invariant of the deviatoric part
333
+
334
+ Notes
335
+ -----
336
+ The Lode angle is defined such that:
337
+
338
+ .. math::
339
+
340
+ \\cos(3\\theta)= \\frac{J_3}{2}\\left(\\frac{3}{J_2}\\right)^{3/2}
341
+ """
342
+ J2 = np.atleast_1d(self.J2)
343
+ J3 = np.atleast_1d(self.J3)
344
+ non_hydro = J2 !=0.
345
+ cosine = np.ones(shape=J3.shape) * np.nan
346
+ cosine[non_hydro] = J3[non_hydro] / 2 * (3 / J2[non_hydro] )**(3 / 2)
347
+ if degrees:
348
+ theta = np.arccos(cosine) * 60 / np.pi
349
+ else:
350
+ theta = np.arccos(cosine) / 3
351
+ if self.shape:
352
+ return theta
353
+ else:
354
+ return theta[0]
355
+
314
356
  def trace(self):
315
357
  """
316
358
  Return the traces of the tensor array
@@ -377,6 +419,18 @@ class SecondOrderTensor:
377
419
  else:
378
420
  raise NotImplementedError('Left multiplication is only implemented for scalar values.')
379
421
 
422
+ def __truediv__(self, other):
423
+ new_mat = np.zeros(self.matrix.shape)
424
+ non_zero = np.any(self.matrix, axis=(-1, -2))
425
+ if isinstance(other, (float, int)):
426
+ new_mat[non_zero] = self.matrix[non_zero] / other # Hack to force 0/0 = 0
427
+ elif isinstance(other, np.ndarray) and (self.shape == other.shape):
428
+ new_mat[non_zero] = np.einsum('pij,p->pij', self.matrix[non_zero], 1/other[non_zero])
429
+ return self.__class__(new_mat)
430
+ else:
431
+ raise NotImplementedError('Tensors can only be divided by scalar values or by arrays of the same shape.')
432
+ return self.__class__(new_mat)
433
+
380
434
  def __eq__(self, other) -> np.ndarray:
381
435
  """
382
436
  Check whether the tensors in the tensor array are equal
@@ -719,7 +773,7 @@ class SecondOrderTensor:
719
773
 
720
774
  Returns
721
775
  -------
722
- SecondOrderTensor
776
+ SkewSymmetricSecondOrderTensor
723
777
  Skew-symmetric tensor
724
778
  """
725
779
  new_mat = 0.5 * (self.matrix - self._transposeTensor())
@@ -731,7 +785,7 @@ class SecondOrderTensor:
731
785
 
732
786
  Returns
733
787
  -------
734
- SecondOrderTensor
788
+ self
735
789
  Spherical part
736
790
 
737
791
  See Also
@@ -748,7 +802,7 @@ class SecondOrderTensor:
748
802
 
749
803
  Returns
750
804
  -------
751
- SecondOrderTensor
805
+ self
752
806
 
753
807
  See Also
754
808
  --------
@@ -769,7 +823,7 @@ class SecondOrderTensor:
769
823
 
770
824
  Returns
771
825
  -------
772
- SecondOrderTensor
826
+ cls
773
827
  Array of identity tensors
774
828
 
775
829
  See Also
@@ -798,7 +852,7 @@ class SecondOrderTensor:
798
852
 
799
853
  Returns
800
854
  -------
801
- SecondOrderTensor
855
+ cls
802
856
  Array of ones tensors
803
857
 
804
858
  See Also
@@ -826,7 +880,7 @@ class SecondOrderTensor:
826
880
 
827
881
  Returns
828
882
  -------
829
- SecondOrderTensor
883
+ cls
830
884
  Array of ones tensors
831
885
 
832
886
  See Also
@@ -855,7 +909,7 @@ class SecondOrderTensor:
855
909
  will be of the same shape as magnitude.
856
910
  Returns
857
911
  -------
858
- SecondOrderTensor
912
+ cls
859
913
  tensor or tensor array
860
914
  """
861
915
  mat = _tensor_from_direction_magnitude(u, u, magnitude)
@@ -875,7 +929,7 @@ class SecondOrderTensor:
875
929
 
876
930
  Returns
877
931
  -------
878
- SecondOrderTensor
932
+ cls
879
933
  Tensor or tensor array of uniform random value
880
934
 
881
935
  See Also
@@ -930,7 +984,7 @@ class SecondOrderTensor:
930
984
 
931
985
  Returns
932
986
  -------
933
- SecondOrderTensor
987
+ cls
934
988
  Tensor or tensor array of normal random value
935
989
  """
936
990
  if shape is None:
@@ -964,7 +1018,7 @@ class SecondOrderTensor:
964
1018
  will be of the same shape as magnitude.
965
1019
  Returns
966
1020
  -------
967
- SecondOrderTensor
1021
+ cls
968
1022
  tensor or tensor array
969
1023
  """
970
1024
  if np.abs(np.dot(u, v)) > 1e-5:
@@ -1281,10 +1335,39 @@ class SymmetricSecondOrderTensor(SecondOrderTensor):
1281
1335
  raise ValueError("array must be of shape (6,) or (...,6) with Voigt vector")
1282
1336
 
1283
1337
  def eig(self):
1284
- return np.linalg.eigh(self.matrix)
1338
+ """
1339
+ Compute the principal values (eigenvaleues) and principal direction (eigenvectors) of the tensor, sorted in
1340
+ descending order of principal values
1341
+
1342
+ Returns
1343
+ -------
1344
+ numpy.ndarray
1345
+ Principal values
1346
+ numpy.ndarray
1347
+ Principal directions
1348
+
1349
+ See Also
1350
+ --------
1351
+ eigvals : compute the principal values only
1352
+ """
1353
+ eigvals, eigdir = np.linalg.eigh(self.matrix)
1354
+ return np.flip(eigvals,axis=-1), np.flip(eigdir,axis=-1)
1285
1355
 
1286
1356
  def eigvals(self):
1287
- return np.linalg.eigvalsh(self.matrix)
1357
+ """
1358
+ Compute the principal values (eigenvalues), sorted in descending order.
1359
+
1360
+ Returns
1361
+ -------
1362
+ numpy.ndarray
1363
+ Principal values
1364
+
1365
+ See Also
1366
+ --------
1367
+ eig : return the principal values and principal directions
1368
+ """
1369
+ eigvals = np.linalg.eigvalsh(self.matrix)
1370
+ return np.flip(eigvals,axis=-1)
1288
1371
 
1289
1372
 
1290
1373
  class SkewSymmetricSecondOrderTensor(SecondOrderTensor):
@@ -1352,7 +1435,8 @@ def rotation_to_matrix(rotation, return_transpose=False):
1352
1435
  if isinstance(rotation, Rotation):
1353
1436
  matrix = rotation.as_matrix()
1354
1437
  elif is_orix_rotation(rotation):
1355
- matrix = rotation.to_matrix()
1438
+ inv_rotation = ~rotation
1439
+ matrix = inv_rotation.to_matrix()
1356
1440
  else:
1357
1441
  raise TypeError('The input argument must be of class scipy.transform.Rotation or '
1358
1442
  'orix.quaternion.rotation.Rotation')
@@ -29,13 +29,14 @@ class StrainTensor(SymmetricSecondOrderTensor):
29
29
 
30
30
  Returns
31
31
  -------
32
- np.array or float
32
+ numpy.ndarray or float
33
33
  Volumetric change
34
34
  """
35
35
  return self.I1
36
36
 
37
37
  def eq_strain(self):
38
- return np.sqrt(3/2 * self.J2)
38
+ """von Mises equivalent strain"""
39
+ return np.sqrt(2/3 * self.ddot(self))
39
40
 
40
41
  def elastic_energy(self, stress):
41
42
  """
@@ -101,7 +102,7 @@ class StressTensor(SymmetricSecondOrderTensor):
101
102
  vonMises : von Mises equivalent stress
102
103
  """
103
104
  ps = self.principal_stresses()
104
- return ps[...,-1] - ps[...,0]
105
+ return ps[...,0] - ps[...,-1]
105
106
 
106
107
  def hydrostaticPressure(self):
107
108
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: elasticipy
3
- Version: 2.8.9
3
+ Version: 2.9.0
4
4
  Summary: A Python library for elasticity tensor computations
5
5
  Author-email: Dorian Depriester <dorian.dep@gmail.com>
6
6
  License: MIT
@@ -1,15 +1,15 @@
1
1
  Elasticipy/CrystalSymmetries.py,sha256=DAX-XPgYqI2nFvf6anCVvZ5fLM0CNSDJ7r2h15l3Hoc,3958
2
2
  Elasticipy/FourthOrderTensor.py,sha256=LuqLxcWeonumxgVCjTIvcIjCkAqMK4ZNBlpGLvXGPeA,54105
3
- Elasticipy/Plasticity.py,sha256=V-jSEf1lKhxKh4yeQs9msZThdT-m_O9KPKVreLk4AvY,4299
3
+ Elasticipy/Plasticity.py,sha256=K6ml5Lfezt-9F2PJvuAUdxsQksZV3bkWYj9GdVTt9gw,10189
4
4
  Elasticipy/PoleFigure.py,sha256=G3Sz7ssX2KKY96g3XEvcaOYFmEat9JUCdb2A91XbA6w,3531
5
- Elasticipy/SecondOrderTensor.py,sha256=fT7WuHYmme5M9lc6rXn8yd81-1SBQ1RKXv2hhkjjG84,43760
5
+ Elasticipy/SecondOrderTensor.py,sha256=0dTZZ6rw9E9ctnyto6cuRajsXk2LmxCcJUOB2YO-u-E,46288
6
6
  Elasticipy/SphericalFunction.py,sha256=X6hrjwCiZkLjiUQ8WO2W4vLNOlbQ4jLWsloOg33IlL4,41244
7
- Elasticipy/StressStrainTensors.py,sha256=nZO6kEePOApdt6Y5anGl47RUhrDkHYtX50jtfKmB0jc,2926
7
+ Elasticipy/StressStrainTensors.py,sha256=WdElPvSl3kpPVF3MNfbQsHtkmm0edqi4Tb2DoE9dD7k,2981
8
8
  Elasticipy/ThermalExpansion.py,sha256=nfPTXuqI4fJe14YLzYn8zfSjpYaep8URRYawj8xzjfo,6012
9
9
  Elasticipy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  Elasticipy/gui.py,sha256=zyVnvp6IIi3FDR5hWfIJrOP28Y7XkP4wc71XXMyGpeo,11247
11
- elasticipy-2.8.9.dist-info/LICENSE,sha256=qNthTMSjVkIDM1_BREgVFQHdn1wVNQi9pwWVfTIazMA,1074
12
- elasticipy-2.8.9.dist-info/METADATA,sha256=dK2QBLG5AhGDCfUUCad7p_-t5m4XAncRxwlYyOCqbGs,3810
13
- elasticipy-2.8.9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
14
- elasticipy-2.8.9.dist-info/top_level.txt,sha256=k4zSQzJR5P4vzlHlhWUaxNgvgloq4KIp8oca2X8gQOw,11
15
- elasticipy-2.8.9.dist-info/RECORD,,
11
+ elasticipy-2.9.0.dist-info/LICENSE,sha256=qNthTMSjVkIDM1_BREgVFQHdn1wVNQi9pwWVfTIazMA,1074
12
+ elasticipy-2.9.0.dist-info/METADATA,sha256=ydDKE8dp7qNcCiQhduU_HIrgIrgWE5YF3_1F7fpx6L0,3810
13
+ elasticipy-2.9.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
14
+ elasticipy-2.9.0.dist-info/top_level.txt,sha256=k4zSQzJR5P4vzlHlhWUaxNgvgloq4KIp8oca2X8gQOw,11
15
+ elasticipy-2.9.0.dist-info/RECORD,,