elasticipy 3.0.0__py3-none-any.whl → 4.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/FourthOrderTensor.py +16 -1779
- Elasticipy/StressStrainTensors.py +16 -138
- Elasticipy/ThermalExpansion.py +9 -246
- Elasticipy/gui.py +2 -2
- Elasticipy/{Plasticity.py → plasticity.py} +1 -1
- Elasticipy/{SphericalFunction.py → spherical_function.py} +1 -1
- Elasticipy/tensors/__init__.py +0 -0
- Elasticipy/tensors/elasticity.py +1656 -0
- Elasticipy/tensors/fourth_order.py +591 -0
- Elasticipy/{SecondOrderTensor.py → tensors/second_order.py} +6 -6
- Elasticipy/tensors/stress_strain.py +138 -0
- Elasticipy/tensors/thermal_expansion.py +249 -0
- {elasticipy-3.0.0.dist-info → elasticipy-4.0.0.dist-info}/METADATA +5 -5
- elasticipy-4.0.0.dist-info/RECORD +20 -0
- {elasticipy-3.0.0.dist-info → elasticipy-4.0.0.dist-info}/WHEEL +1 -1
- elasticipy-3.0.0.dist-info/RECORD +0 -15
- /Elasticipy/{CrystalSymmetries.py → crystal_symmetries.py} +0 -0
- /Elasticipy/{PoleFigure.py → polefigure.py} +0 -0
- {elasticipy-3.0.0.dist-info → elasticipy-4.0.0.dist-info/licenses}/LICENSE +0 -0
- {elasticipy-3.0.0.dist-info → elasticipy-4.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from Elasticipy.tensors.second_order import SymmetricSecondOrderTensor, rotation_to_matrix, is_orix_rotation, \
|
|
3
|
+
SecondOrderTensor, ALPHABET
|
|
4
|
+
from scipy.spatial.transform import Rotation
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
|
|
7
|
+
a = np.sqrt(2)
|
|
8
|
+
KELVIN_MAPPING_MATRIX = np.array([[1, 1, 1, a, a, a],
|
|
9
|
+
[1, 1, 1, a, a, a],
|
|
10
|
+
[1, 1, 1, a, a, a],
|
|
11
|
+
[a, a, a, 2, 2, 2],
|
|
12
|
+
[a, a, a, 2, 2, 2],
|
|
13
|
+
[a, a, a, 2, 2, 2], ])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def voigt_indices(i, j):
|
|
17
|
+
"""
|
|
18
|
+
Translate the two-index notation to one-index notation
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
i : int or np.ndarray
|
|
23
|
+
First index
|
|
24
|
+
j : int or np.ndarray
|
|
25
|
+
Second index
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
Index in the vector of length 6
|
|
30
|
+
"""
|
|
31
|
+
voigt_mat = np.array([[0, 5, 4],
|
|
32
|
+
[5, 1, 3],
|
|
33
|
+
[4, 3, 2]])
|
|
34
|
+
return voigt_mat[i, j]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def unvoigt_index(i):
|
|
38
|
+
"""
|
|
39
|
+
Translate the one-index notation to two-index notation
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
i : int or np.ndarray
|
|
44
|
+
Index to translate
|
|
45
|
+
"""
|
|
46
|
+
inverse_voigt_mat = np.array([[0, 0],
|
|
47
|
+
[1, 1],
|
|
48
|
+
[2, 2],
|
|
49
|
+
[1, 2],
|
|
50
|
+
[0, 2],
|
|
51
|
+
[0, 1]])
|
|
52
|
+
return inverse_voigt_mat[i]
|
|
53
|
+
|
|
54
|
+
def rotate_tensor(full_tensor, r):
|
|
55
|
+
"""
|
|
56
|
+
Rotate a (full) fourth-order tensor.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
full_tensor : numpy.ndarray
|
|
61
|
+
array of shape (3,3,3,3) or (...,3,3,3,3) containing all the components
|
|
62
|
+
r : scipy.spatial.Rotation or orix.quaternion.Rotation
|
|
63
|
+
Rotation, or set of rotations, to apply
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
numpy.ndarray
|
|
68
|
+
Rotated tensor. If r is an array, the corresponding axes will be added as first axes in the result array.
|
|
69
|
+
"""
|
|
70
|
+
rot_mat = rotation_to_matrix(r)
|
|
71
|
+
str_ein = '...im,...jn,...ko,...lp,...mnop->...ijkl'
|
|
72
|
+
return np.einsum(str_ein, rot_mat, rot_mat, rot_mat, rot_mat, full_tensor)
|
|
73
|
+
|
|
74
|
+
class FourthOrderTensor:
|
|
75
|
+
"""
|
|
76
|
+
Template class for manipulating symmetric fourth-order tensors.
|
|
77
|
+
|
|
78
|
+
Attributes
|
|
79
|
+
----------
|
|
80
|
+
matrix : np.ndarray
|
|
81
|
+
(6,6) matrix gathering all the components of the tensor, using the Voigt notation.
|
|
82
|
+
"""
|
|
83
|
+
tensor_name = '4th-order'
|
|
84
|
+
|
|
85
|
+
def __init__(self, M, mapping='Kelvin', check_minor_symmetry=True, force_minor_symmetry=False):
|
|
86
|
+
"""
|
|
87
|
+
Construct of Fourth-order tensor with minor symmetry.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
M : np.ndarray
|
|
92
|
+
(6,6) matrix corresponding to the stiffness tensor, written using the Voigt notation, or array of shape
|
|
93
|
+
(3,3,3,3).
|
|
94
|
+
mapping : str or list of list, or numpy/ndarray, optional
|
|
95
|
+
Mapping convention to translate the (3,3,3,3) array to (6,6) matrix
|
|
96
|
+
check_minor_symmetry : bool, optional
|
|
97
|
+
If true (default), check that the input array have minor symmetries (see Notes). Only used if an array of
|
|
98
|
+
shape (...,3,3,3,3) is passed.
|
|
99
|
+
force_minor_symmetry :
|
|
100
|
+
Ensure that the tensor displays minor symmetry.
|
|
101
|
+
|
|
102
|
+
Notes
|
|
103
|
+
-----
|
|
104
|
+
The minor symmetry is defined so that:
|
|
105
|
+
|
|
106
|
+
.. math::
|
|
107
|
+
|
|
108
|
+
M_{ijkl}=M_{jikl}=M_{jilk}=M_{ijlk}
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
if isinstance(mapping, (list, tuple, np.ndarray)):
|
|
112
|
+
self.mapping_matrix = mapping
|
|
113
|
+
self.mapping_name = 'custom'
|
|
114
|
+
else:
|
|
115
|
+
mapping = mapping.capitalize()
|
|
116
|
+
self.mapping_name = mapping
|
|
117
|
+
if mapping == 'Kelvin':
|
|
118
|
+
self.mapping_matrix = KELVIN_MAPPING_MATRIX
|
|
119
|
+
elif mapping == 'Voigt':
|
|
120
|
+
self.mapping_matrix = np.ones((6,6))
|
|
121
|
+
else:
|
|
122
|
+
raise ValueError('The mapping to use can be either "Kelvin", "Voigt", or a (6,6) matrix.')
|
|
123
|
+
|
|
124
|
+
M = np.asarray(M)
|
|
125
|
+
if M.shape[-2:] == (6, 6):
|
|
126
|
+
matrix = M
|
|
127
|
+
elif M.shape[-4:] == (3, 3, 3, 3):
|
|
128
|
+
Mijlk = np.swapaxes(M, -1, -2)
|
|
129
|
+
Mjikl = np.swapaxes(M, -3, -4)
|
|
130
|
+
Mjilk = np.swapaxes(Mjikl, -1, -2)
|
|
131
|
+
if force_minor_symmetry:
|
|
132
|
+
M = 0.25 * (M + Mijlk + Mjikl + Mjilk)
|
|
133
|
+
elif check_minor_symmetry:
|
|
134
|
+
symmetry = np.all(M == Mijlk) and np.all(M == Mjikl) and np.all(M == Mjilk)
|
|
135
|
+
if not symmetry:
|
|
136
|
+
raise ValueError('The input array does not have minor symmetry')
|
|
137
|
+
matrix = self._full_to_matrix(M)
|
|
138
|
+
else:
|
|
139
|
+
raise ValueError('The input matrix must of shape (...,6,6) or (...,3,3,3,3)')
|
|
140
|
+
self.matrix = matrix
|
|
141
|
+
for i in range(0, 6):
|
|
142
|
+
for j in range(0, 6):
|
|
143
|
+
def getter(obj, I=i, J=j):
|
|
144
|
+
return obj.matrix[...,I, J]
|
|
145
|
+
|
|
146
|
+
getter.__doc__ = f"Returns the ({i + 1},{j + 1}) component of the {self.tensor_name} matrix."
|
|
147
|
+
component_name = 'C{}{}'.format(i + 1, j + 1)
|
|
148
|
+
setattr(self.__class__, component_name, property(getter)) # Dynamically create the property
|
|
149
|
+
|
|
150
|
+
def __repr__(self):
|
|
151
|
+
if (self.ndim == 0) or ((self.ndim==1) and self.shape[0]<5):
|
|
152
|
+
msg = '{} tensor (in {} mapping):\n'.format(self.tensor_name, self.mapping_name)
|
|
153
|
+
msg += self.matrix.__str__()
|
|
154
|
+
else:
|
|
155
|
+
msg = '{} tensor array of shape {}'.format(self.tensor_name, self.shape)
|
|
156
|
+
return msg
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def shape(self):
|
|
160
|
+
"""
|
|
161
|
+
Return the shape of the tensor array
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
tuple
|
|
165
|
+
Shape of the tensor array
|
|
166
|
+
"""
|
|
167
|
+
*shape, _, _ = self.matrix.shape
|
|
168
|
+
return tuple(shape)
|
|
169
|
+
|
|
170
|
+
def full_tensor(self):
|
|
171
|
+
"""
|
|
172
|
+
Returns the full (unvoigted) tensor, as a [3, 3, 3, 3] array
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
np.ndarray
|
|
177
|
+
Full tensor (4-index notation)
|
|
178
|
+
"""
|
|
179
|
+
i, j, k, ell = np.indices((3, 3, 3, 3))
|
|
180
|
+
ij = voigt_indices(i, j)
|
|
181
|
+
kl = voigt_indices(k, ell)
|
|
182
|
+
m = self.matrix[..., ij, kl] / self.mapping_matrix[ij, kl]
|
|
183
|
+
return m
|
|
184
|
+
|
|
185
|
+
def flatten(self):
|
|
186
|
+
"""
|
|
187
|
+
Flatten the tensor
|
|
188
|
+
|
|
189
|
+
If the tensor array is of shape (m,n,o...,r), the flattened array will be of shape (m*n*o*...*r,).
|
|
190
|
+
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
SymmetricFourthOrderTensor
|
|
194
|
+
Flattened tensor
|
|
195
|
+
"""
|
|
196
|
+
shape = self.shape
|
|
197
|
+
if shape:
|
|
198
|
+
t2 = deepcopy(self)
|
|
199
|
+
p = (np.prod(self.shape), 6, 6)
|
|
200
|
+
t2.matrix = self.matrix.reshape(p)
|
|
201
|
+
return t2
|
|
202
|
+
else:
|
|
203
|
+
return self
|
|
204
|
+
|
|
205
|
+
def _full_to_matrix(self, full_tensor):
|
|
206
|
+
kl, ij = np.indices((6, 6))
|
|
207
|
+
i, j = unvoigt_index(ij).T
|
|
208
|
+
k, ell = unvoigt_index(kl).T
|
|
209
|
+
return full_tensor[..., i, j, k, ell] * self.mapping_matrix[ij, kl]
|
|
210
|
+
|
|
211
|
+
def rotate(self, rotation):
|
|
212
|
+
"""
|
|
213
|
+
Apply a single rotation to a tensor, and return its component into the rotated frame.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
rotation : Rotation or orix.quaternion.rotation.Rotation
|
|
218
|
+
Rotation to apply
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
SymmetricFourthOrderTensor
|
|
223
|
+
Rotated tensor
|
|
224
|
+
"""
|
|
225
|
+
t2 = deepcopy(self)
|
|
226
|
+
rotated_tensor = rotate_tensor(self.full_tensor(), rotation)
|
|
227
|
+
t2.matrix = self._full_to_matrix(rotated_tensor)
|
|
228
|
+
return t2
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def ndim(self):
|
|
232
|
+
"""
|
|
233
|
+
Returns the dimensionality of the tensor (number of dimensions in the orientation array)
|
|
234
|
+
|
|
235
|
+
Returns
|
|
236
|
+
-------
|
|
237
|
+
int
|
|
238
|
+
Number of dimensions
|
|
239
|
+
"""
|
|
240
|
+
shape = self.shape
|
|
241
|
+
if shape:
|
|
242
|
+
return len(shape)
|
|
243
|
+
else:
|
|
244
|
+
return 0
|
|
245
|
+
|
|
246
|
+
def mean(self, axis=None):
|
|
247
|
+
"""
|
|
248
|
+
Compute the mean value of the tensor T
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
axis : int or list of int or tuple of int, optional
|
|
253
|
+
axis along which to compute the mean. If None, the mean is computed on the flattened tensor
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
numpy.ndarray
|
|
258
|
+
If no axis is given, the result will be of shape (3,3,3,3).
|
|
259
|
+
Otherwise, if T.ndim=m, and len(axis)=n, the returned value will be of shape (...,3,3,3,3), with ndim=m-n+4
|
|
260
|
+
"""
|
|
261
|
+
t2 = deepcopy(self)
|
|
262
|
+
if axis is None:
|
|
263
|
+
axis = tuple([i for i in range(0,self.ndim)])
|
|
264
|
+
t2.matrix = np.mean(self.matrix, axis=axis)
|
|
265
|
+
return t2
|
|
266
|
+
|
|
267
|
+
def __add__(self, other):
|
|
268
|
+
if isinstance(other, np.ndarray):
|
|
269
|
+
if other.shape == (6, 6):
|
|
270
|
+
mat = self.matrix + other
|
|
271
|
+
elif other.shape == (3, 3, 3, 3):
|
|
272
|
+
mat = self._full_to_matrix(self.full_tensor() + other)
|
|
273
|
+
else:
|
|
274
|
+
raise ValueError('The input argument must be either a 6x6 matrix or a (3,3,3,3) array.')
|
|
275
|
+
elif isinstance(other, FourthOrderTensor):
|
|
276
|
+
if type(other) == type(self):
|
|
277
|
+
mat = self.full_tensor() + other.full_tensor()
|
|
278
|
+
else:
|
|
279
|
+
raise ValueError('The two tensors to add must be of the same class.')
|
|
280
|
+
else:
|
|
281
|
+
raise ValueError('I don''t know how to add {} with {}.'.format(type(self), type(other)))
|
|
282
|
+
return self.__class__(mat, mapping=self.mapping_matrix)
|
|
283
|
+
|
|
284
|
+
def __sub__(self, other):
|
|
285
|
+
if isinstance(other, FourthOrderTensor):
|
|
286
|
+
return self.__add__(-other.matrix)
|
|
287
|
+
else:
|
|
288
|
+
return self.__add__(-other)
|
|
289
|
+
|
|
290
|
+
def __neg__(self):
|
|
291
|
+
t = deepcopy(self)
|
|
292
|
+
t.matrix = -t.matrix
|
|
293
|
+
return t
|
|
294
|
+
|
|
295
|
+
def ddot(self, other, mode='pair'):
|
|
296
|
+
"""
|
|
297
|
+
Perform tensor product contracted twice (":") between two fourth-order tensors
|
|
298
|
+
|
|
299
|
+
Parameters
|
|
300
|
+
----------
|
|
301
|
+
other : FourthOrderTensor or SecondOrderTensor
|
|
302
|
+
Right-hand side of ":" symbol
|
|
303
|
+
mode : str, optional
|
|
304
|
+
If mode=="pair", the tensors must be broadcastable, and the tensor product are performed on the last axes.
|
|
305
|
+
If mode=="cross", all cross-combinations are considered.
|
|
306
|
+
|
|
307
|
+
Returns
|
|
308
|
+
-------
|
|
309
|
+
FourthOrderTensor or numpy.ndarray
|
|
310
|
+
If both the tensors are 0D (no orientation), the return value will be of type SymmetricTensor
|
|
311
|
+
Otherwise, the return value will be the full tensor, of shape (...,3,3,3,3).
|
|
312
|
+
"""
|
|
313
|
+
if isinstance(other, FourthOrderTensor):
|
|
314
|
+
if self.ndim == 0 and other.ndim == 0:
|
|
315
|
+
return FourthOrderTensor(np.einsum('ijmn,nmkl->ijkl', self.full_tensor(), other.full_tensor()))
|
|
316
|
+
else:
|
|
317
|
+
if mode == 'pair':
|
|
318
|
+
ein_str = '...ijmn,...nmkl->...ijkl'
|
|
319
|
+
else:
|
|
320
|
+
ndim_0 = self.ndim
|
|
321
|
+
ndim_1 = other.ndim
|
|
322
|
+
indices_0 = ALPHABET[:ndim_0]
|
|
323
|
+
indices_1 = ALPHABET[:ndim_1].upper()
|
|
324
|
+
indices_2 = indices_0 + indices_1
|
|
325
|
+
ein_str = indices_0 + 'wxXY,' + indices_1 + 'YXyz->' + indices_2 + 'wxyz'
|
|
326
|
+
matrix = np.einsum(ein_str, self.full_tensor(), other.full_tensor())
|
|
327
|
+
return FourthOrderTensor(matrix)
|
|
328
|
+
elif isinstance(other, SecondOrderTensor):
|
|
329
|
+
if self.ndim == 0 and other.ndim == 0:
|
|
330
|
+
return SymmetricSecondOrderTensor(np.einsum('ijkl,kl->ij', self.full_tensor(), other.matrix))
|
|
331
|
+
else:
|
|
332
|
+
if mode == 'pair':
|
|
333
|
+
ein_str = '...ijkl,...kl->...ij'
|
|
334
|
+
else:
|
|
335
|
+
ndim_0 = self.ndim
|
|
336
|
+
ndim_1 = other.ndim
|
|
337
|
+
indices_0 = ALPHABET[:ndim_0]
|
|
338
|
+
indices_1 = ALPHABET[:ndim_1].upper()
|
|
339
|
+
indices_2 = indices_0 + indices_1
|
|
340
|
+
ein_str = indices_0 + 'wxXY,' + indices_1 + 'XY->' + indices_2 + 'wx'
|
|
341
|
+
matrix = np.einsum(ein_str, self.full_tensor(), other.matrix)
|
|
342
|
+
return SecondOrderTensor(matrix)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def __mul__(self, other):
|
|
346
|
+
if isinstance(other, (FourthOrderTensor, SecondOrderTensor)):
|
|
347
|
+
return self.ddot(other)
|
|
348
|
+
elif isinstance(other, np.ndarray):
|
|
349
|
+
shape = other.shape
|
|
350
|
+
if other.shape == self.shape[-len(shape):]:
|
|
351
|
+
matrix = self.matrix * other[...,np.newaxis, np.newaxis]
|
|
352
|
+
return self.__class__(matrix)
|
|
353
|
+
else:
|
|
354
|
+
raise ValueError('The arrays to multiply could not be broadcasted with shapes {} and {}'.format(self.shape, other.shape[:-2]))
|
|
355
|
+
elif isinstance(other, Rotation) or is_orix_rotation(other):
|
|
356
|
+
return self.rotate(other)
|
|
357
|
+
else:
|
|
358
|
+
return self.__class__(self.matrix * other)
|
|
359
|
+
|
|
360
|
+
def __truediv__(self, other):
|
|
361
|
+
if isinstance(other, (SecondOrderTensor, FourthOrderTensor)):
|
|
362
|
+
return self * other.inv()
|
|
363
|
+
else:
|
|
364
|
+
return self * (1 / other)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def transpose_array(self):
|
|
368
|
+
"""
|
|
369
|
+
Transpose the orientations of the tensor array
|
|
370
|
+
|
|
371
|
+
Returns
|
|
372
|
+
-------
|
|
373
|
+
FourthOrderTensor
|
|
374
|
+
The same tensor, but with transposed axes
|
|
375
|
+
"""
|
|
376
|
+
ndim = self.ndim
|
|
377
|
+
if ndim==0 or ndim==1:
|
|
378
|
+
return self
|
|
379
|
+
else:
|
|
380
|
+
new_axes = tuple(range(ndim))[::-1] + (ndim, ndim + 1)
|
|
381
|
+
transposed_matrix = self.matrix.transpose(new_axes)
|
|
382
|
+
return self.__class__(transposed_matrix)
|
|
383
|
+
|
|
384
|
+
def __rmul__(self, other):
|
|
385
|
+
if isinstance(other, (Rotation, float, int, np.number)) or is_orix_rotation(other):
|
|
386
|
+
return self * other
|
|
387
|
+
else:
|
|
388
|
+
raise NotImplementedError('A fourth order tensor can be left-multiplied by rotations or scalar only.')
|
|
389
|
+
|
|
390
|
+
def __eq__(self, other):
|
|
391
|
+
if isinstance(other, FourthOrderTensor):
|
|
392
|
+
return np.all(self.matrix == other.matrix, axis=(-1,-2))
|
|
393
|
+
elif isinstance(other, (float, int)) or (isinstance(other, np.ndarray) and other.shape[-2:] == (6, 6)):
|
|
394
|
+
return np.all(self.matrix == other, axis=(-1,-2))
|
|
395
|
+
else:
|
|
396
|
+
raise NotImplementedError('The element to compare with must be a fourth-order tensor '
|
|
397
|
+
'or an array of shape (6,6).')
|
|
398
|
+
|
|
399
|
+
def __getitem__(self, item):
|
|
400
|
+
if self.ndim:
|
|
401
|
+
sub_mat= self.matrix[item]
|
|
402
|
+
if sub_mat.shape[-2:] != (6,6):
|
|
403
|
+
raise IndexError('Too many indices for tensor array: array is {}-dimensional, but {} were provided'.format(self.ndim, len(item)))
|
|
404
|
+
else:
|
|
405
|
+
return self.__class__(sub_mat)
|
|
406
|
+
else:
|
|
407
|
+
raise IndexError('A single tensor cannot be subindexed')
|
|
408
|
+
|
|
409
|
+
def __setitem__(self, index, value):
|
|
410
|
+
if isinstance(value, np.ndarray):
|
|
411
|
+
if value.shape[-2:] == (6,6):
|
|
412
|
+
self.matrix[index] = value
|
|
413
|
+
elif value.shape[-4:] == (3,3,3,3):
|
|
414
|
+
submatrix = self._full_to_matrix(value)
|
|
415
|
+
self.matrix[index] = submatrix
|
|
416
|
+
else:
|
|
417
|
+
return ValueError('The R.h.s must be either of shape (...,6,6) or (...,3,3,3,3)')
|
|
418
|
+
elif isinstance(value, FourthOrderTensor):
|
|
419
|
+
self.matrix[index] = value.matrix / value.mapping_matrix * self.mapping_matrix
|
|
420
|
+
else:
|
|
421
|
+
raise NotImplementedError('The r.h.s must be either an ndarray or an object of class {}'.format(self.__class__))
|
|
422
|
+
|
|
423
|
+
@classmethod
|
|
424
|
+
def identity(cls, shape=(), return_full_tensor=False, mapping='Kelvin'):
|
|
425
|
+
"""
|
|
426
|
+
Create a 4th-order identity tensor
|
|
427
|
+
|
|
428
|
+
Parameters
|
|
429
|
+
----------
|
|
430
|
+
shape : int or tuple, optional
|
|
431
|
+
Shape of the tensor to create
|
|
432
|
+
return_full_tensor : bool, optional
|
|
433
|
+
If True, return the full tensor as a (3,3,3,3) or a (...,3,3,3,3) array. Otherwise, the tensor is returned
|
|
434
|
+
as a SymmetricTensor object.
|
|
435
|
+
|
|
436
|
+
Returns
|
|
437
|
+
-------
|
|
438
|
+
numpy.ndarray or SymmetricTensor
|
|
439
|
+
Identity tensor
|
|
440
|
+
"""
|
|
441
|
+
eye = np.eye(3)
|
|
442
|
+
if isinstance(shape, int):
|
|
443
|
+
shape = (shape,)
|
|
444
|
+
if len(shape):
|
|
445
|
+
for n in np.flip(shape):
|
|
446
|
+
eye = np.repeat(eye[np.newaxis,...], n, axis=0)
|
|
447
|
+
a = np.einsum('...ik,...jl->...ijkl', eye, eye)
|
|
448
|
+
b = np.einsum('...il,...jk->...ijkl', eye, eye)
|
|
449
|
+
full = 0.5*(a + b)
|
|
450
|
+
if return_full_tensor:
|
|
451
|
+
return full
|
|
452
|
+
else:
|
|
453
|
+
return cls(full, mapping=mapping)
|
|
454
|
+
|
|
455
|
+
def inv(self):
|
|
456
|
+
"""
|
|
457
|
+
Invert the tensor. The inverted tensors inherits the properties (if any)
|
|
458
|
+
|
|
459
|
+
Returns
|
|
460
|
+
-------
|
|
461
|
+
FourthOrderTensor
|
|
462
|
+
Inverse tensor
|
|
463
|
+
"""
|
|
464
|
+
t2 = deepcopy(self)
|
|
465
|
+
new_matrix = np.linalg.inv(self.matrix)
|
|
466
|
+
t2.matrix = new_matrix
|
|
467
|
+
return t2
|
|
468
|
+
|
|
469
|
+
@classmethod
|
|
470
|
+
def zeros(cls, shape=()):
|
|
471
|
+
"""
|
|
472
|
+
Create a fourth-order tensor populated with zeros
|
|
473
|
+
|
|
474
|
+
Parameters
|
|
475
|
+
----------
|
|
476
|
+
shape : int or tuple, optional
|
|
477
|
+
Shape of the tensor to create
|
|
478
|
+
Returns
|
|
479
|
+
-------
|
|
480
|
+
FourthOrderTensor
|
|
481
|
+
"""
|
|
482
|
+
if isinstance(shape, int):
|
|
483
|
+
shape = (shape, 6, 6)
|
|
484
|
+
else:
|
|
485
|
+
shape = shape + (6,6)
|
|
486
|
+
zeros = np.zeros(shape)
|
|
487
|
+
return cls(zeros)
|
|
488
|
+
|
|
489
|
+
class SymmetricFourthOrderTensor(FourthOrderTensor):
|
|
490
|
+
tensor_name = 'Symmetric 4th-order'
|
|
491
|
+
|
|
492
|
+
def __init__(self, M, check_symmetries=True, force_symmetries=False, **kwargs):
|
|
493
|
+
"""
|
|
494
|
+
Construct a fully symmetric fourth-order tensor from a (...,6,6) or a (3,3,3,3) array.
|
|
495
|
+
|
|
496
|
+
The input matrix must be symmetric, otherwise an error is thrown (except if check_symmetry==False, see below)
|
|
497
|
+
|
|
498
|
+
Parameters
|
|
499
|
+
----------
|
|
500
|
+
M : np.ndarray
|
|
501
|
+
(6,6) matrix corresponding to the stiffness tensor, written using the Voigt notation, or array of shape
|
|
502
|
+
(3,3,3,3).
|
|
503
|
+
check_symmetries : bool, optional
|
|
504
|
+
Whether to check or not that the tensor to built displays both major and minor symmetries (see Notes).
|
|
505
|
+
force_symmetries : bool, optional
|
|
506
|
+
If true, ensure that the tensor displays both minor and major symmetries.
|
|
507
|
+
|
|
508
|
+
Notes
|
|
509
|
+
-----
|
|
510
|
+
The major symmetry is defined so that:
|
|
511
|
+
|
|
512
|
+
.. math::
|
|
513
|
+
|
|
514
|
+
M_{ijkl}=M_{klij}
|
|
515
|
+
|
|
516
|
+
whereas the minor symmetry is:
|
|
517
|
+
|
|
518
|
+
.. math::
|
|
519
|
+
|
|
520
|
+
M_{ijkl}=M_{jikl}=M_{jilk}=M_{ijlk}
|
|
521
|
+
"""
|
|
522
|
+
super().__init__(M, check_minor_symmetry=check_symmetries, force_minor_symmetry=force_symmetries, **kwargs)
|
|
523
|
+
if force_symmetries:
|
|
524
|
+
self.matrix = 0.5*(self.matrix + self.matrix.swapaxes(-1,-2))
|
|
525
|
+
elif check_symmetries and not np.all(np.isclose(self.matrix, self.matrix.swapaxes(-1, -2))):
|
|
526
|
+
raise ValueError('The input matrix must be symmetric')
|
|
527
|
+
|
|
528
|
+
def invariants(self, order='all'):
|
|
529
|
+
"""
|
|
530
|
+
Compute the invariants of the tensor.
|
|
531
|
+
|
|
532
|
+
Compute the linear or/and quadratic invariant of the fourth-order tensor (see notes)
|
|
533
|
+
|
|
534
|
+
Parameters
|
|
535
|
+
----------
|
|
536
|
+
order : str, optional
|
|
537
|
+
If 'linear', only A1 and A2 are returned
|
|
538
|
+
If 'quadratic', A1², A2², B1, B2, B3, B4 and B5 are returned
|
|
539
|
+
If 'all' (default), A1, A2, A1², A2², B1, B2, B3, B4 and B5 are returned
|
|
540
|
+
|
|
541
|
+
Returns
|
|
542
|
+
-------
|
|
543
|
+
tuple
|
|
544
|
+
invariants of the given order (see above)
|
|
545
|
+
|
|
546
|
+
Notes
|
|
547
|
+
-----
|
|
548
|
+
The nomenclature of the invariants follows that of [4]_. The linear invariants are:
|
|
549
|
+
|
|
550
|
+
.. math::
|
|
551
|
+
|
|
552
|
+
A_1=C_{ijij}
|
|
553
|
+
|
|
554
|
+
A_2=C_{iijj}
|
|
555
|
+
|
|
556
|
+
whereas the quadratic invariants are:
|
|
557
|
+
|
|
558
|
+
.. math::
|
|
559
|
+
|
|
560
|
+
B_1 = C_{ijkl}C_{ijkl}
|
|
561
|
+
|
|
562
|
+
B_2 = C_{iikl}C_{jjkl}
|
|
563
|
+
|
|
564
|
+
B_3 = C_{iikl}C_{jkjl}
|
|
565
|
+
|
|
566
|
+
B_4 = C_{kiil}C_{kjjl}
|
|
567
|
+
|
|
568
|
+
B_5 = C_{ijkl}C_{ikjl}
|
|
569
|
+
|
|
570
|
+
References
|
|
571
|
+
----------
|
|
572
|
+
.. [4] Norris, A. N. (22 May 2007). "Quadratic invariants of elastic moduli". The Quarterly Journal of Mechanics
|
|
573
|
+
and Applied Mathematics. 60 (3): 367–389. doi:10.1093/qjmam/hbm007
|
|
574
|
+
"""
|
|
575
|
+
t = self.full_tensor()
|
|
576
|
+
order = order.lower()
|
|
577
|
+
A1 = np.einsum('...ijij->...',t)
|
|
578
|
+
A2 = np.einsum('...iijj->...',t)
|
|
579
|
+
lin_inv = (A1, A2)
|
|
580
|
+
if order == 'linear':
|
|
581
|
+
return lin_inv
|
|
582
|
+
B1 = np.einsum('...ijkl,...ijkl->...',t, t)
|
|
583
|
+
B2 = np.einsum('...iikl,...jjkl->...', t, t)
|
|
584
|
+
B3 = np.einsum('...iikl,...jkjl->...', t, t)
|
|
585
|
+
B4 = np.einsum('...kiil,...kjjl->...', t, t)
|
|
586
|
+
B5 = np.einsum('...ijkl,...ikjl->...', t, t)
|
|
587
|
+
quad_inv = (A1**2, A2**2, A1*A2, B1, B2, B3, B4, B5)
|
|
588
|
+
if order == 'quadratic':
|
|
589
|
+
return quad_inv
|
|
590
|
+
else:
|
|
591
|
+
return lin_inv + quad_inv
|
|
@@ -552,7 +552,7 @@ class SecondOrderTensor:
|
|
|
552
552
|
|
|
553
553
|
Examples
|
|
554
554
|
--------
|
|
555
|
-
>>> from Elasticipy.
|
|
555
|
+
>>> from Elasticipy.tensors.second_order import SecondOrderTensor
|
|
556
556
|
>>> A=SecondOrderTensor.rand(10)
|
|
557
557
|
>>> B=SecondOrderTensor.rand(10)
|
|
558
558
|
>>> AB_pair = A.dot(B)
|
|
@@ -1078,7 +1078,7 @@ class SecondOrderTensor:
|
|
|
1078
1078
|
--------
|
|
1079
1079
|
Generate a single random tensor:
|
|
1080
1080
|
|
|
1081
|
-
>>> from Elasticipy.
|
|
1081
|
+
>>> from Elasticipy.tensors.second_order import SecondOrderTensor as tensor
|
|
1082
1082
|
>>> tensor.rand(seed=123)
|
|
1083
1083
|
Second-order tensor
|
|
1084
1084
|
[[0.68235186 0.05382102 0.22035987]
|
|
@@ -1364,7 +1364,7 @@ class SecondOrderTensor:
|
|
|
1364
1364
|
flatten : Converts a tensor array to 1D tensor array
|
|
1365
1365
|
"""
|
|
1366
1366
|
try:
|
|
1367
|
-
from Elasticipy.
|
|
1367
|
+
from Elasticipy.tensors.stress_strain import StrainTensor, StressTensor
|
|
1368
1368
|
if isinstance(self, StrainTensor):
|
|
1369
1369
|
from pymatgen.analysis.elasticity import Strain as Constructor
|
|
1370
1370
|
elif isinstance(self, StressTensor):
|
|
@@ -1403,7 +1403,7 @@ class SymmetricSecondOrderTensor(SecondOrderTensor):
|
|
|
1403
1403
|
--------
|
|
1404
1404
|
We can create a symmetric tensor by privoding the full matrix, as long it is symmetric:
|
|
1405
1405
|
|
|
1406
|
-
>>> from Elasticipy.
|
|
1406
|
+
>>> from Elasticipy.tensors.second_order import SymmetricSecondOrderTensor
|
|
1407
1407
|
>>> a = SymmetricSecondOrderTensor([[11, 12, 13],[12, 22, 23],[13, 23, 33]])
|
|
1408
1408
|
>>> print(a)
|
|
1409
1409
|
Symmetric second-order tensor
|
|
@@ -1462,7 +1462,7 @@ class SymmetricSecondOrderTensor(SecondOrderTensor):
|
|
|
1462
1462
|
|
|
1463
1463
|
Examples
|
|
1464
1464
|
--------
|
|
1465
|
-
>>> from Elasticipy.
|
|
1465
|
+
>>> from Elasticipy.tensors.second_order import SymmetricSecondOrderTensor
|
|
1466
1466
|
>>> SymmetricSecondOrderTensor.from_Voigt([11, 22, 33, 23, 13, 12])
|
|
1467
1467
|
Symmetric second-order tensor
|
|
1468
1468
|
[[11. 12. 13.]
|
|
@@ -1573,7 +1573,7 @@ class SkewSymmetricSecondOrderTensor(SecondOrderTensor):
|
|
|
1573
1573
|
--------
|
|
1574
1574
|
One can construct a skew-symmetric tensor by providing the full skew-symmetric matrix:
|
|
1575
1575
|
|
|
1576
|
-
>>> from Elasticipy.
|
|
1576
|
+
>>> from Elasticipy.tensors.second_order import SkewSymmetricSecondOrderTensor
|
|
1577
1577
|
>>> a = SkewSymmetricSecondOrderTensor([[0, 12, 13],[-12, 0, 23],[-13, -23, 0]])
|
|
1578
1578
|
>>> print(a)
|
|
1579
1579
|
Skew-symmetric second-order tensor
|