elasticipy 4.2.0__py3-none-any.whl → 5.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/gui.py +101 -2
- Elasticipy/interfaces/FEPX.py +3 -3
- Elasticipy/plasticity.py +179 -34
- Elasticipy/spherical_function.py +43 -4
- Elasticipy/tensors/elasticity.py +667 -141
- Elasticipy/tensors/fourth_order.py +895 -172
- Elasticipy/tensors/mapping.py +48 -0
- Elasticipy/tensors/second_order.py +49 -7
- Elasticipy/tensors/stress_strain.py +209 -3
- Elasticipy/tensors/thermal_expansion.py +6 -1
- {elasticipy-4.2.0.dist-info → elasticipy-5.0.0.dist-info}/METADATA +40 -21
- elasticipy-5.0.0.dist-info/RECORD +24 -0
- elasticipy-5.0.0.dist-info/entry_points.txt +2 -0
- elasticipy-4.2.0.dist-info/RECORD +0 -23
- {elasticipy-4.2.0.dist-info → elasticipy-5.0.0.dist-info}/WHEEL +0 -0
- {elasticipy-4.2.0.dist-info → elasticipy-5.0.0.dist-info}/licenses/LICENSE +0 -0
- {elasticipy-4.2.0.dist-info → elasticipy-5.0.0.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,7 @@ from scipy.spatial.transform import Rotation
|
|
|
5
5
|
from copy import deepcopy
|
|
6
6
|
from Elasticipy.tensors.mapping import KelvinMapping, VoigtMapping
|
|
7
7
|
|
|
8
|
+
kelvin_mapping = KelvinMapping()
|
|
8
9
|
|
|
9
10
|
def voigt_indices(i, j):
|
|
10
11
|
"""
|
|
@@ -44,48 +45,48 @@ def unvoigt_index(i):
|
|
|
44
45
|
[0, 1]])
|
|
45
46
|
return inverse_voigt_mat[i]
|
|
46
47
|
|
|
47
|
-
def
|
|
48
|
-
"""
|
|
49
|
-
Rotate a (full) fourth-order tensor.
|
|
50
|
-
|
|
51
|
-
Parameters
|
|
52
|
-
----------
|
|
53
|
-
full_tensor : numpy.ndarray
|
|
54
|
-
array of shape (3,3,3,3) or (...,3,3,3,3) containing all the components
|
|
55
|
-
r : scipy.spatial.Rotation or orix.quaternion.Rotation
|
|
56
|
-
Rotation, or set of rotations, to apply
|
|
57
|
-
|
|
58
|
-
Returns
|
|
59
|
-
-------
|
|
60
|
-
numpy.ndarray
|
|
61
|
-
Rotated tensor. If r is an array, the corresponding axes will be added as first axes in the result array.
|
|
62
|
-
"""
|
|
48
|
+
def _rotate_tensor(full_tensor, r):
|
|
63
49
|
rot_mat = rotation_to_matrix(r)
|
|
64
50
|
str_ein = '...im,...jn,...ko,...lp,...mnop->...ijkl'
|
|
65
51
|
return np.einsum(str_ein, rot_mat, rot_mat, rot_mat, rot_mat, full_tensor)
|
|
66
52
|
|
|
53
|
+
def _isotropic_matrix(C11, C12, C44):
|
|
54
|
+
C11 = np.asarray(C11)
|
|
55
|
+
C12 = np.asarray(C12)
|
|
56
|
+
C44 = np.asarray(C44)
|
|
57
|
+
shape = np.broadcast_shapes(C11.shape, C12.shape, C44.shape)
|
|
58
|
+
matrix = np.zeros(shape=shape + (6, 6))
|
|
59
|
+
matrix[..., 0, 0] = C11
|
|
60
|
+
matrix[..., 1, 1] = C11
|
|
61
|
+
matrix[..., 2, 2] = C11
|
|
62
|
+
matrix[..., 0, 1] = matrix[..., 1, 0] = C12
|
|
63
|
+
matrix[..., 0, 2] = matrix[..., 2, 0] = C12
|
|
64
|
+
matrix[..., 1, 2] = matrix[..., 2, 1] = C12
|
|
65
|
+
matrix[..., 3, 3] = C44
|
|
66
|
+
matrix[..., 4, 4] = C44
|
|
67
|
+
matrix[..., 5, 5] = C44
|
|
68
|
+
return matrix
|
|
69
|
+
|
|
67
70
|
class FourthOrderTensor:
|
|
68
71
|
"""
|
|
69
72
|
Template class for manipulating symmetric fourth-order tensors.
|
|
70
|
-
|
|
71
|
-
Attributes
|
|
72
|
-
----------
|
|
73
|
-
_matrix : np.ndarray
|
|
74
|
-
(6,6) matrix gathering all the components of the tensor, using the Voigt notation.
|
|
75
73
|
"""
|
|
76
|
-
|
|
74
|
+
_tensor_name = '4th-order'
|
|
77
75
|
|
|
78
|
-
def
|
|
76
|
+
def _array_to_Kelvin(self, matrix):
|
|
77
|
+
return matrix / self.mapping.matrix * kelvin_mapping.matrix
|
|
78
|
+
|
|
79
|
+
def __init__(self, M, mapping=kelvin_mapping, check_minor_symmetry=True, force_minor_symmetry=False):
|
|
79
80
|
"""
|
|
80
81
|
Construct of Fourth-order tensor with minor symmetry.
|
|
81
82
|
|
|
82
83
|
Parameters
|
|
83
84
|
----------
|
|
84
|
-
M : np.ndarray
|
|
85
|
-
(6,6) matrix corresponding to the stiffness tensor, written using the Voigt notation, or array of shape
|
|
86
|
-
(3,3,3,3).
|
|
85
|
+
M : np.ndarray or FourthOrderTensor
|
|
86
|
+
(...,6,6) matrix corresponding to the stiffness tensor, written using the Voigt notation, or array of shape
|
|
87
|
+
(...,3,3,3,3).
|
|
87
88
|
mapping : MappingConvention, optional
|
|
88
|
-
Mapping convention to translate the (3,3,3,3) array to (6,6) matrix
|
|
89
|
+
Mapping convention to translate the (3,3,3,3) array to (6,6) matrix.
|
|
89
90
|
check_minor_symmetry : bool, optional
|
|
90
91
|
If true (default), check that the input array have minor symmetries (see Notes). Only used if an array of
|
|
91
92
|
shape (...,3,3,3,3) is passed.
|
|
@@ -100,46 +101,168 @@ class FourthOrderTensor:
|
|
|
100
101
|
|
|
101
102
|
M_{ijkl}=M_{jikl}=M_{jilk}=M_{ijlk}
|
|
102
103
|
|
|
104
|
+
Given a generic 4th-order tensor T, the corresponding matrix with respect to Kelvin convention is:
|
|
105
|
+
|
|
106
|
+
.. math::
|
|
107
|
+
|
|
108
|
+
T =
|
|
109
|
+
\\begin{bmatrix}
|
|
110
|
+
T_{1111} & T_{1122} & T_{1133} & \\sqrt{2}T_{1123} & \\sqrt{2}T_{1113} & \\sqrt{2}T_{1112}\\\\
|
|
111
|
+
T_{2211} & T_{2222} & T_{2233} & \\sqrt{2}T_{2223} & \\sqrt{2}T_{2213} & \\sqrt{2}T_{2212}\\\\
|
|
112
|
+
T_{3311} & T_{3322} & T_{3333} & \\sqrt{2}T_{3323} & \\sqrt{2}T_{3313} & \\sqrt{2}T_{3312}\\\\
|
|
113
|
+
\\sqrt{2}T_{2311} & \\sqrt{2}T_{2322} & \\sqrt{2}T_{2333} & 2T_{2323} & 2T_{2313} & 2T_{2312}\\\\
|
|
114
|
+
\\sqrt{2}T_{1311} & \\sqrt{2}T_{1322} & \\sqrt{2}T_{1333} & 2T_{423} & 2T_{1313} & 2T_{1312}\\\\
|
|
115
|
+
\\sqrt{2}T_{1211} & \\sqrt{2}T_{1222} & \\sqrt{2}T_{1233} & 2T_{1223} & 2T_{1223} & 2T_{1212}\\\\
|
|
116
|
+
\\end{bmatrix}
|
|
117
|
+
|
|
118
|
+
Examples
|
|
119
|
+
--------
|
|
120
|
+
Consider a Fourth-order tensor, whose Kelvin matrix is:
|
|
121
|
+
|
|
122
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
123
|
+
>>> import numpy as np
|
|
124
|
+
>>> mat = np.array([[100, 200, 300, 0, 0, 0],
|
|
125
|
+
... [-200, 100, 50, 0, 0, 0],
|
|
126
|
+
... [-300, -50, 100, 0, 0, 0],
|
|
127
|
+
... [0, 0, 0, 150, 0, 0],
|
|
128
|
+
... [0, 0, 0, 0, 150, 0],
|
|
129
|
+
... [0, 0, 0, 0, 0, 150]])
|
|
130
|
+
>>> T = FourthOrderTensor(mat)
|
|
131
|
+
>>> print(T)
|
|
132
|
+
4th-order tensor (in Kelvin mapping):
|
|
133
|
+
[[ 100. 200. 300. 0. 0. 0.]
|
|
134
|
+
[-200. 100. 50. 0. 0. 0.]
|
|
135
|
+
[-300. -50. 100. 0. 0. 0.]
|
|
136
|
+
[ 0. 0. 0. 150. 0. 0.]
|
|
137
|
+
[ 0. 0. 0. 0. 150. 0.]
|
|
138
|
+
[ 0. 0. 0. 0. 0. 150.]]
|
|
139
|
+
|
|
140
|
+
If one wants to evaluate the tensor as a (full) (3,3,3,3) array:
|
|
141
|
+
|
|
142
|
+
>>> T_array = T.full_tensor
|
|
143
|
+
|
|
144
|
+
For instance:
|
|
145
|
+
|
|
146
|
+
>>> T_array[0,0,0,0]
|
|
147
|
+
100.0
|
|
148
|
+
|
|
149
|
+
whereas
|
|
150
|
+
|
|
151
|
+
>>> T_array[0,1,0,1] # Corresponds to T_{66}/2
|
|
152
|
+
75.0
|
|
153
|
+
|
|
154
|
+
The half factor comes from the Kelvin mapping convention (see Notes). One can also use the Voigt mapping to
|
|
155
|
+
avoid this:
|
|
156
|
+
|
|
157
|
+
>>> from Elasticipy.tensors.mapping import VoigtMapping
|
|
158
|
+
>>> T_voigt = FourthOrderTensor(mat, mapping=VoigtMapping())
|
|
159
|
+
>>> print(T_voigt)
|
|
160
|
+
4th-order tensor (in Voigt mapping):
|
|
161
|
+
[[ 100. 200. 300. 0. 0. 0.]
|
|
162
|
+
[-200. 100. 50. 0. 0. 0.]
|
|
163
|
+
[-300. -50. 100. 0. 0. 0.]
|
|
164
|
+
[ 0. 0. 0. 150. 0. 0.]
|
|
165
|
+
[ 0. 0. 0. 0. 150. 0.]
|
|
166
|
+
[ 0. 0. 0. 0. 0. 150.]]
|
|
167
|
+
|
|
168
|
+
Although T and T_voigt appear to be the same, note that they are not expressed using the same mapping
|
|
169
|
+
convention. Indeed:
|
|
170
|
+
|
|
171
|
+
>>> T_voigt.full_tensor[0,0,0,0]
|
|
172
|
+
100.0
|
|
173
|
+
|
|
174
|
+
whereas
|
|
175
|
+
|
|
176
|
+
>>> T_voigt.full_tensor[0,1,0,1]
|
|
177
|
+
150.0
|
|
178
|
+
|
|
179
|
+
Alternatively, the differences can be checked with:
|
|
180
|
+
|
|
181
|
+
>>> T == T_voigt
|
|
182
|
+
False
|
|
183
|
+
|
|
184
|
+
Conversely, let consider the following Voigt matrix:
|
|
185
|
+
|
|
186
|
+
>>> mat = np.array([[100, 200, 300, 0, 0, 0],
|
|
187
|
+
... [-200, 100, 50, 0, 0, 0],
|
|
188
|
+
... [-300, -50, 100, 0, 0, 0],
|
|
189
|
+
... [0, 0, 0, 75, 0, 0],
|
|
190
|
+
... [0, 0, 0, 0, 75, 0],
|
|
191
|
+
... [0, 0, 0, 0, 0, 75]])
|
|
192
|
+
>>> T_voigt2 = FourthOrderTensor(mat, mapping=VoigtMapping())
|
|
193
|
+
>>> print(T_voigt2)
|
|
194
|
+
4th-order tensor (in Voigt mapping):
|
|
195
|
+
[[ 100. 200. 300. 0. 0. 0.]
|
|
196
|
+
[-200. 100. 50. 0. 0. 0.]
|
|
197
|
+
[-300. -50. 100. 0. 0. 0.]
|
|
198
|
+
[ 0. 0. 0. 75. 0. 0.]
|
|
199
|
+
[ 0. 0. 0. 0. 75. 0.]
|
|
200
|
+
[ 0. 0. 0. 0. 0. 75.]]
|
|
201
|
+
|
|
202
|
+
Although T and T_voigt2 are not written using the same mapping, we can compare them:
|
|
203
|
+
|
|
204
|
+
>>> T == T_voigt2 # Same tensors, but different mapping
|
|
205
|
+
True
|
|
206
|
+
|
|
207
|
+
whereas
|
|
208
|
+
|
|
209
|
+
>>> T == T_voigt # Different tensors, but same mapping
|
|
210
|
+
False
|
|
211
|
+
|
|
212
|
+
This property comes from the fact that the comparison is made independently of the underlying mapping convention.
|
|
103
213
|
"""
|
|
214
|
+
if isinstance(mapping, str):
|
|
215
|
+
if mapping.lower() == 'voigt':
|
|
216
|
+
mapping = VoigtMapping()
|
|
217
|
+
elif mapping.lower() == 'kelvin':
|
|
218
|
+
mapping = kelvin_mapping
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError('Mapping must be either "voigt" or "kelvin"')
|
|
104
221
|
self.mapping=mapping
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
matrix = M
|
|
108
|
-
elif M.shape[-4:] == (3, 3, 3, 3):
|
|
109
|
-
Mijlk = np.swapaxes(M, -1, -2)
|
|
110
|
-
Mjikl = np.swapaxes(M, -3, -4)
|
|
111
|
-
Mjilk = np.swapaxes(Mjikl, -1, -2)
|
|
112
|
-
if force_minor_symmetry:
|
|
113
|
-
M = 0.25 * (M + Mijlk + Mjikl + Mjilk)
|
|
114
|
-
elif check_minor_symmetry:
|
|
115
|
-
symmetry = np.all(M == Mijlk) and np.all(M == Mjikl) and np.all(M == Mjilk)
|
|
116
|
-
if not symmetry:
|
|
117
|
-
raise ValueError('The input array does not have minor symmetry')
|
|
118
|
-
matrix = self._full_to_matrix(M)
|
|
222
|
+
if isinstance(M, FourthOrderTensor):
|
|
223
|
+
self._matrix = M._matrix
|
|
119
224
|
else:
|
|
120
|
-
|
|
121
|
-
|
|
225
|
+
M = np.asarray(M)
|
|
226
|
+
if M.shape[-2:] == (6, 6):
|
|
227
|
+
matrix = self._array_to_Kelvin(M)
|
|
228
|
+
elif M.shape[-4:] == (3, 3, 3, 3):
|
|
229
|
+
Mijlk = np.swapaxes(M, -1, -2)
|
|
230
|
+
Mjikl = np.swapaxes(M, -3, -4)
|
|
231
|
+
Mjilk = np.swapaxes(Mjikl, -1, -2)
|
|
232
|
+
if force_minor_symmetry:
|
|
233
|
+
M = 0.25 * (M + Mijlk + Mjikl + Mjilk)
|
|
234
|
+
elif check_minor_symmetry:
|
|
235
|
+
symmetry = np.all(M == Mijlk) and np.all(M == Mjikl) and np.all(M == Mjilk)
|
|
236
|
+
if not symmetry:
|
|
237
|
+
raise ValueError('The input array does not have minor symmetry')
|
|
238
|
+
matrix = self._full_to_matrix(M)
|
|
239
|
+
else:
|
|
240
|
+
raise ValueError('The input matrix must of shape (...,6,6) or (...,3,3,3,3)')
|
|
241
|
+
self._matrix = matrix
|
|
122
242
|
for i in range(0, 6):
|
|
123
243
|
for j in range(0, 6):
|
|
124
244
|
def getter(obj, I=i, J=j):
|
|
125
|
-
|
|
245
|
+
new_matrix = obj._matrix / kelvin_mapping.matrix * self.mapping.matrix
|
|
246
|
+
return new_matrix[...,I, J]
|
|
126
247
|
|
|
127
|
-
getter.__doc__ = f"Returns the ({i + 1},{j + 1}) component of the {self.
|
|
248
|
+
getter.__doc__ = f"Returns the ({i + 1},{j + 1}) component of the {self._tensor_name} matrix."
|
|
128
249
|
component_name = 'C{}{}'.format(i + 1, j + 1)
|
|
129
250
|
setattr(self.__class__, component_name, property(getter)) # Dynamically create the property
|
|
130
251
|
|
|
131
252
|
def __repr__(self):
|
|
132
253
|
if (self.ndim == 0) or ((self.ndim==1) and self.shape[0]<5):
|
|
133
|
-
msg = '{} tensor (in {} mapping):\n'.format(self.
|
|
134
|
-
|
|
254
|
+
msg = '{} tensor (in {} mapping):\n'.format(self._tensor_name, self.mapping.name)
|
|
255
|
+
matrix = self.matrix(self.mapping)
|
|
256
|
+
msg += matrix.__str__()
|
|
135
257
|
else:
|
|
136
|
-
msg = '{} tensor array of shape {}'.format(self.
|
|
258
|
+
msg = '{} tensor array of shape {}'.format(self._tensor_name, self.shape)
|
|
137
259
|
return msg
|
|
138
260
|
|
|
139
261
|
@property
|
|
140
262
|
def shape(self):
|
|
141
263
|
"""
|
|
142
264
|
Return the shape of the tensor array
|
|
265
|
+
|
|
143
266
|
Returns
|
|
144
267
|
-------
|
|
145
268
|
tuple
|
|
@@ -148,19 +271,46 @@ class FourthOrderTensor:
|
|
|
148
271
|
*shape, _, _ = self._matrix.shape
|
|
149
272
|
return tuple(shape)
|
|
150
273
|
|
|
274
|
+
@property
|
|
151
275
|
def full_tensor(self):
|
|
152
276
|
"""
|
|
153
|
-
Returns the full (unvoigted) tensor
|
|
277
|
+
Returns the full (unvoigted) tensor as a (3, 3, 3, 3) or (..., 3, 3, 3, 3) array
|
|
154
278
|
|
|
155
279
|
Returns
|
|
156
280
|
-------
|
|
157
281
|
np.ndarray
|
|
158
282
|
Full tensor (4-index notation)
|
|
283
|
+
|
|
284
|
+
Examples
|
|
285
|
+
--------
|
|
286
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
287
|
+
>>> I = FourthOrderTensor.eye() # 4th order identity tensor
|
|
288
|
+
>>> print(I)
|
|
289
|
+
4th-order tensor (in Kelvin mapping):
|
|
290
|
+
[[1. 0. 0. 0. 0. 0.]
|
|
291
|
+
[0. 1. 0. 0. 0. 0.]
|
|
292
|
+
[0. 0. 1. 0. 0. 0.]
|
|
293
|
+
[0. 0. 0. 1. 0. 0.]
|
|
294
|
+
[0. 0. 0. 0. 1. 0.]
|
|
295
|
+
[0. 0. 0. 0. 0. 1.]]
|
|
296
|
+
|
|
297
|
+
>>> I_full = I.full_tensor
|
|
298
|
+
>>> type(I_full)
|
|
299
|
+
<class 'numpy.ndarray'>
|
|
300
|
+
>>> I_full.shape
|
|
301
|
+
(3, 3, 3, 3)
|
|
302
|
+
|
|
303
|
+
When working on tensor arrays, the shape of the resulting numpy array will change accordlingly. E.g.:
|
|
304
|
+
|
|
305
|
+
>>> I_array = FourthOrderTensor.eye(shape=(5,6)) # Array of 4th order identity tensor
|
|
306
|
+
>>> I_array.full_tensor.shape
|
|
307
|
+
(5, 6, 3, 3, 3, 3)
|
|
159
308
|
"""
|
|
160
309
|
i, j, k, ell = np.indices((3, 3, 3, 3))
|
|
161
310
|
ij = voigt_indices(i, j)
|
|
162
311
|
kl = voigt_indices(k, ell)
|
|
163
|
-
|
|
312
|
+
matrix = self._matrix / kelvin_mapping.matrix
|
|
313
|
+
m = matrix[..., ij, kl]
|
|
164
314
|
return m
|
|
165
315
|
|
|
166
316
|
def flatten(self):
|
|
@@ -173,6 +323,15 @@ class FourthOrderTensor:
|
|
|
173
323
|
-------
|
|
174
324
|
SymmetricFourthOrderTensor
|
|
175
325
|
Flattened tensor
|
|
326
|
+
|
|
327
|
+
Examples
|
|
328
|
+
--------
|
|
329
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
330
|
+
>>> T = FourthOrderTensor.rand(shape=(5,6))
|
|
331
|
+
>>> T
|
|
332
|
+
4th-order tensor array of shape (5, 6)
|
|
333
|
+
>>> T.flatten()
|
|
334
|
+
4th-order tensor array of shape (30,)
|
|
176
335
|
"""
|
|
177
336
|
shape = self.shape
|
|
178
337
|
if shape:
|
|
@@ -187,7 +346,7 @@ class FourthOrderTensor:
|
|
|
187
346
|
kl, ij = np.indices((6, 6))
|
|
188
347
|
i, j = unvoigt_index(ij).T
|
|
189
348
|
k, ell = unvoigt_index(kl).T
|
|
190
|
-
return full_tensor[..., i, j, k, ell] *
|
|
349
|
+
return full_tensor[..., i, j, k, ell] * kelvin_mapping.matrix[ij, kl]
|
|
191
350
|
|
|
192
351
|
def rotate(self, rotation):
|
|
193
352
|
"""
|
|
@@ -202,9 +361,72 @@ class FourthOrderTensor:
|
|
|
202
361
|
-------
|
|
203
362
|
SymmetricFourthOrderTensor
|
|
204
363
|
Rotated tensor
|
|
364
|
+
|
|
365
|
+
Examples
|
|
366
|
+
--------
|
|
367
|
+
Let start from a given tensor, (say ones):
|
|
368
|
+
|
|
369
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
370
|
+
>>> T = FourthOrderTensor.ones()
|
|
371
|
+
>>> T
|
|
372
|
+
4th-order tensor (in Kelvin mapping):
|
|
373
|
+
[[1. 1. 1. 1.41421356 1.41421356 1.41421356]
|
|
374
|
+
[1. 1. 1. 1.41421356 1.41421356 1.41421356]
|
|
375
|
+
[1. 1. 1. 1.41421356 1.41421356 1.41421356]
|
|
376
|
+
[1.41421356 1.41421356 1.41421356 2. 2. 2. ]
|
|
377
|
+
[1.41421356 1.41421356 1.41421356 2. 2. 2. ]
|
|
378
|
+
[1.41421356 1.41421356 1.41421356 2. 2. 2. ]]
|
|
379
|
+
|
|
380
|
+
Define a rotation. E.g.:
|
|
381
|
+
|
|
382
|
+
>>> from scipy.spatial.transform import Rotation
|
|
383
|
+
>>> g = Rotation.from_euler('X', 90, degrees=True)
|
|
384
|
+
|
|
385
|
+
Then , apply rotation:
|
|
386
|
+
|
|
387
|
+
>>> Trotated = T.rotate(g)
|
|
388
|
+
>>> Trotated
|
|
389
|
+
4th-order tensor (in Kelvin mapping):
|
|
390
|
+
[[ 1. 1. 1. -1.41421356 1.41421356 -1.41421356]
|
|
391
|
+
[ 1. 1. 1. -1.41421356 1.41421356 -1.41421356]
|
|
392
|
+
[ 1. 1. 1. -1.41421356 1.41421356 -1.41421356]
|
|
393
|
+
[-1.41421356 -1.41421356 -1.41421356 2. -2. 2. ]
|
|
394
|
+
[ 1.41421356 1.41421356 1.41421356 -2. 2. -2. ]
|
|
395
|
+
[-1.41421356 -1.41421356 -1.41421356 2. -2. 2. ]]
|
|
396
|
+
|
|
397
|
+
Actually, a more simple syntax is:
|
|
398
|
+
|
|
399
|
+
>>> T * g
|
|
400
|
+
4th-order tensor (in Kelvin mapping):
|
|
401
|
+
[[ 1. 1. 1. -1.41421356 1.41421356 -1.41421356]
|
|
402
|
+
[ 1. 1. 1. -1.41421356 1.41421356 -1.41421356]
|
|
403
|
+
[ 1. 1. 1. -1.41421356 1.41421356 -1.41421356]
|
|
404
|
+
[-1.41421356 -1.41421356 -1.41421356 2. -2. 2. ]
|
|
405
|
+
[ 1.41421356 1.41421356 1.41421356 -2. 2. -2. ]
|
|
406
|
+
[-1.41421356 -1.41421356 -1.41421356 2. -2. 2. ]]
|
|
407
|
+
|
|
408
|
+
Obviously, the original tensor can be retrieved by applying the reverse rotation:
|
|
409
|
+
|
|
410
|
+
>>> Trotated * g.inv()
|
|
411
|
+
4th-order tensor (in Kelvin mapping):
|
|
412
|
+
[[1. 1. 1. 1.41421356 1.41421356 1.41421356]
|
|
413
|
+
[1. 1. 1. 1.41421356 1.41421356 1.41421356]
|
|
414
|
+
[1. 1. 1. 1.41421356 1.41421356 1.41421356]
|
|
415
|
+
[1.41421356 1.41421356 1.41421356 2. 2. 2. ]
|
|
416
|
+
[1.41421356 1.41421356 1.41421356 2. 2. 2. ]
|
|
417
|
+
[1.41421356 1.41421356 1.41421356 2. 2. 2. ]]
|
|
418
|
+
|
|
419
|
+
If ``g`` is composed of multiple rotations, this will result in a tensor array, corresponding to each rotation:
|
|
420
|
+
|
|
421
|
+
>>> import numpy as np
|
|
422
|
+
>>> theta = np.linspace(0, 90, 100)
|
|
423
|
+
>>> g = Rotation.from_euler('X', theta, degrees=True)
|
|
424
|
+
>>> Trotated = T * g
|
|
425
|
+
>>> Trotated
|
|
426
|
+
4th-order tensor array of shape (100,)
|
|
205
427
|
"""
|
|
206
428
|
t2 = deepcopy(self)
|
|
207
|
-
rotated_tensor =
|
|
429
|
+
rotated_tensor = _rotate_tensor(self.full_tensor, rotation)
|
|
208
430
|
t2._matrix = self._full_to_matrix(rotated_tensor)
|
|
209
431
|
return t2
|
|
210
432
|
|
|
@@ -237,7 +459,31 @@ class FourthOrderTensor:
|
|
|
237
459
|
-------
|
|
238
460
|
numpy.ndarray
|
|
239
461
|
If no axis is given, the result will be of shape (3,3,3,3).
|
|
240
|
-
|
|
462
|
+
|
|
463
|
+
Examples
|
|
464
|
+
--------
|
|
465
|
+
Create a random tensor array of shape (5,6):
|
|
466
|
+
|
|
467
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
468
|
+
>>> T = FourthOrderTensor.rand(shape=(5,6))
|
|
469
|
+
>>> Overall_mean = T.mean()
|
|
470
|
+
>>> Overall_mean.shape
|
|
471
|
+
()
|
|
472
|
+
>>> Overall_mean # doctest: +SKIP
|
|
473
|
+
4th-order tensor (in Kelvin mapping):
|
|
474
|
+
[[0.514295 0.52259217 0.42899181 0.77148692 0.64073221 0.73211491]
|
|
475
|
+
[0.49422678 0.43718365 0.40786118 0.8170971 0.68435571 0.67262655]
|
|
476
|
+
[0.48753674 0.51142541 0.44650454 0.76310921 0.67724973 0.69430165]
|
|
477
|
+
[0.53946846 0.75101474 0.73578098 1.04338905 1.21598419 0.99489014]
|
|
478
|
+
[0.75354555 0.61193555 0.82341479 1.11197826 0.89183143 1.20986243]
|
|
479
|
+
[0.66078807 0.70126535 0.63719147 0.87567139 1.05671229 1.03004098]]
|
|
480
|
+
|
|
481
|
+
>>> axis_0_mean = T.mean(axis=0)
|
|
482
|
+
>>> axis_0_mean.shape
|
|
483
|
+
(6,)
|
|
484
|
+
>>> axis_1_mean = T.mean(axis=1)
|
|
485
|
+
>>> axis_1_mean.shape
|
|
486
|
+
(5,)
|
|
241
487
|
"""
|
|
242
488
|
t2 = deepcopy(self)
|
|
243
489
|
if axis is None:
|
|
@@ -246,27 +492,26 @@ class FourthOrderTensor:
|
|
|
246
492
|
return t2
|
|
247
493
|
|
|
248
494
|
def __add__(self, other):
|
|
495
|
+
new_tensor = deepcopy(self)
|
|
249
496
|
if isinstance(other, np.ndarray):
|
|
250
|
-
if other.shape == (6, 6):
|
|
251
|
-
mat = self._matrix + other
|
|
497
|
+
if other.shape[-2:] == (6, 6):
|
|
498
|
+
mat = self._matrix + self._array_to_Kelvin(other)
|
|
252
499
|
elif other.shape == (3, 3, 3, 3):
|
|
253
|
-
mat = self._full_to_matrix(self.full_tensor
|
|
500
|
+
mat = self._full_to_matrix(self.full_tensor + other)
|
|
254
501
|
else:
|
|
255
502
|
raise ValueError('The input argument must be either a 6x6 matrix or a (3,3,3,3) array.')
|
|
256
503
|
elif isinstance(other, FourthOrderTensor):
|
|
257
504
|
if type(other) == type(self):
|
|
258
|
-
mat = self.
|
|
505
|
+
mat = self._matrix + other._matrix
|
|
259
506
|
else:
|
|
260
507
|
raise ValueError('The two tensors to add must be of the same class.')
|
|
261
508
|
else:
|
|
262
509
|
raise ValueError('I don''t know how to add {} with {}.'.format(type(self), type(other)))
|
|
263
|
-
|
|
510
|
+
new_tensor._matrix = mat
|
|
511
|
+
return new_tensor
|
|
264
512
|
|
|
265
513
|
def __sub__(self, other):
|
|
266
|
-
|
|
267
|
-
return self.__add__(-other._matrix)
|
|
268
|
-
else:
|
|
269
|
-
return self.__add__(-other)
|
|
514
|
+
return self.__add__(-other)
|
|
270
515
|
|
|
271
516
|
def __neg__(self):
|
|
272
517
|
t = deepcopy(self)
|
|
@@ -287,13 +532,49 @@ class FourthOrderTensor:
|
|
|
287
532
|
|
|
288
533
|
Returns
|
|
289
534
|
-------
|
|
290
|
-
FourthOrderTensor
|
|
291
|
-
|
|
292
|
-
|
|
535
|
+
FourthOrderTensor
|
|
536
|
+
Result from double-contraction
|
|
537
|
+
|
|
538
|
+
Examples
|
|
539
|
+
--------
|
|
540
|
+
First, let consider two random arrays of Fourth-order tensors:
|
|
541
|
+
|
|
542
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
543
|
+
>>> T1 = FourthOrderTensor.rand(shape=(2,3))
|
|
544
|
+
>>> T2 = FourthOrderTensor.rand(shape=3)
|
|
545
|
+
>>> T1T2_pair = T1.ddot(T2)
|
|
546
|
+
>>> T1T2_pair
|
|
547
|
+
4th-order tensor array of shape (2, 3)
|
|
548
|
+
|
|
549
|
+
whereas:
|
|
550
|
+
|
|
551
|
+
>>> T1T2_cross = T1.ddot(T2, mode='cross')
|
|
552
|
+
>>> T1T2_cross
|
|
553
|
+
4th-order tensor array of shape (2, 3, 3)
|
|
554
|
+
|
|
555
|
+
The command above is equivalent (but way faster) to:
|
|
556
|
+
|
|
557
|
+
>>> T1T2_cross_loop = FourthOrderTensor.zeros(shape=(2,3,3))
|
|
558
|
+
>>> for i in range(2):
|
|
559
|
+
... for j in range(3):
|
|
560
|
+
... for k in range(3):
|
|
561
|
+
... T1T2_cross_loop[i,j,k] = T1[i,j].ddot(T2[k])
|
|
562
|
+
|
|
563
|
+
One can check that the results are consistent with:
|
|
564
|
+
|
|
565
|
+
>>> T1T2_cross_loop == T1T2_cross
|
|
566
|
+
array([[[ True, True, True],
|
|
567
|
+
[ True, True, True],
|
|
568
|
+
[ True, True, True]],
|
|
569
|
+
<BLANKLINE>
|
|
570
|
+
[[ True, True, True],
|
|
571
|
+
[ True, True, True],
|
|
572
|
+
[ True, True, True]]])
|
|
573
|
+
|
|
293
574
|
"""
|
|
294
575
|
if isinstance(other, FourthOrderTensor):
|
|
295
576
|
if self.ndim == 0 and other.ndim == 0:
|
|
296
|
-
return FourthOrderTensor(np.einsum('ijmn,nmkl->ijkl', self.full_tensor
|
|
577
|
+
return FourthOrderTensor(np.einsum('ijmn,nmkl->ijkl', self.full_tensor, other.full_tensor))
|
|
297
578
|
else:
|
|
298
579
|
if mode == 'pair':
|
|
299
580
|
ein_str = '...ijmn,...nmkl->...ijkl'
|
|
@@ -304,11 +585,11 @@ class FourthOrderTensor:
|
|
|
304
585
|
indices_1 = ALPHABET[:ndim_1].upper()
|
|
305
586
|
indices_2 = indices_0 + indices_1
|
|
306
587
|
ein_str = indices_0 + 'wxXY,' + indices_1 + 'YXyz->' + indices_2 + 'wxyz'
|
|
307
|
-
matrix = np.einsum(ein_str, self.full_tensor
|
|
588
|
+
matrix = np.einsum(ein_str, self.full_tensor, other.full_tensor)
|
|
308
589
|
return FourthOrderTensor(matrix)
|
|
309
590
|
elif isinstance(other, SecondOrderTensor):
|
|
310
591
|
if self.ndim == 0 and other.ndim == 0:
|
|
311
|
-
return SymmetricSecondOrderTensor(np.einsum('ijkl,kl->ij', self.full_tensor
|
|
592
|
+
return SymmetricSecondOrderTensor(np.einsum('ijkl,kl->ij', self.full_tensor, other.matrix))
|
|
312
593
|
else:
|
|
313
594
|
if mode == 'pair':
|
|
314
595
|
ein_str = '...ijkl,...kl->...ij'
|
|
@@ -319,9 +600,36 @@ class FourthOrderTensor:
|
|
|
319
600
|
indices_1 = ALPHABET[:ndim_1].upper()
|
|
320
601
|
indices_2 = indices_0 + indices_1
|
|
321
602
|
ein_str = indices_0 + 'wxXY,' + indices_1 + 'XY->' + indices_2 + 'wx'
|
|
322
|
-
matrix = np.einsum(ein_str, self.full_tensor
|
|
603
|
+
matrix = np.einsum(ein_str, self.full_tensor, other.matrix)
|
|
323
604
|
return SecondOrderTensor(matrix)
|
|
324
605
|
|
|
606
|
+
@classmethod
|
|
607
|
+
def rand(cls, shape=None, **kwargs):
|
|
608
|
+
"""
|
|
609
|
+
Populate a Fourth-order tensor with random values in half-open interval [0.0, 1.0).
|
|
610
|
+
|
|
611
|
+
Parameters
|
|
612
|
+
----------
|
|
613
|
+
shape : tuple or int, optional
|
|
614
|
+
Set the shape of the tensor array. If None, the returned tensor will be single.
|
|
615
|
+
kwargs
|
|
616
|
+
Keyword arguments passed to the Fourth-order tensor constructor.
|
|
617
|
+
|
|
618
|
+
Returns
|
|
619
|
+
-------
|
|
620
|
+
FourthOrderTensor
|
|
621
|
+
Fourth-order tensor
|
|
622
|
+
"""
|
|
623
|
+
if shape is None:
|
|
624
|
+
shape = (6,6)
|
|
625
|
+
elif isinstance(shape, int):
|
|
626
|
+
shape = (shape, 6, 6)
|
|
627
|
+
else:
|
|
628
|
+
shape = tuple(shape) + (6,6)
|
|
629
|
+
mat = np.random.random_sample(shape)
|
|
630
|
+
t = FourthOrderTensor(mat, **kwargs)
|
|
631
|
+
t._matrix = t._matrix * t.mapping.matrix
|
|
632
|
+
return t
|
|
325
633
|
|
|
326
634
|
def __mul__(self, other):
|
|
327
635
|
if isinstance(other, (FourthOrderTensor, SecondOrderTensor)):
|
|
@@ -336,7 +644,9 @@ class FourthOrderTensor:
|
|
|
336
644
|
elif isinstance(other, Rotation) or is_orix_rotation(other):
|
|
337
645
|
return self.rotate(other)
|
|
338
646
|
else:
|
|
339
|
-
|
|
647
|
+
new_tensor = deepcopy(self)
|
|
648
|
+
new_tensor._matrix = self._matrix * other
|
|
649
|
+
return new_tensor
|
|
340
650
|
|
|
341
651
|
def __truediv__(self, other):
|
|
342
652
|
if isinstance(other, (SecondOrderTensor, FourthOrderTensor)):
|
|
@@ -353,14 +663,22 @@ class FourthOrderTensor:
|
|
|
353
663
|
-------
|
|
354
664
|
FourthOrderTensor
|
|
355
665
|
The same tensor, but with transposed axes
|
|
666
|
+
|
|
667
|
+
Examples
|
|
668
|
+
--------
|
|
669
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
670
|
+
>>> A = FourthOrderTensor.rand(shape=(3,4))
|
|
671
|
+
>>> A.transpose_array()
|
|
672
|
+
4th-order tensor array of shape (4, 3)
|
|
356
673
|
"""
|
|
357
674
|
ndim = self.ndim
|
|
358
675
|
if ndim==0 or ndim==1:
|
|
359
676
|
return self
|
|
360
677
|
else:
|
|
678
|
+
new_array = deepcopy(self)
|
|
361
679
|
new_axes = tuple(range(ndim))[::-1] + (ndim, ndim + 1)
|
|
362
|
-
|
|
363
|
-
return
|
|
680
|
+
new_array._matrix = self._matrix.transpose(new_axes)
|
|
681
|
+
return new_array
|
|
364
682
|
|
|
365
683
|
def __rmul__(self, other):
|
|
366
684
|
if isinstance(other, (Rotation, float, int, np.number)) or is_orix_rotation(other):
|
|
@@ -379,18 +697,16 @@ class FourthOrderTensor:
|
|
|
379
697
|
|
|
380
698
|
def __getitem__(self, item):
|
|
381
699
|
if self.ndim:
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
else:
|
|
386
|
-
return self.__class__(sub_mat)
|
|
700
|
+
sub_tensor = deepcopy(self)
|
|
701
|
+
sub_tensor._matrix = self._matrix[item]
|
|
702
|
+
return sub_tensor
|
|
387
703
|
else:
|
|
388
704
|
raise IndexError('A single tensor cannot be subindexed')
|
|
389
705
|
|
|
390
706
|
def __setitem__(self, index, value):
|
|
391
707
|
if isinstance(value, np.ndarray):
|
|
392
708
|
if value.shape[-2:] == (6,6):
|
|
393
|
-
self._matrix[index] = value
|
|
709
|
+
self._matrix[index] = value / self.mapping.matrix * kelvin_mapping.matrix
|
|
394
710
|
elif value.shape[-4:] == (3,3,3,3):
|
|
395
711
|
submatrix = self._full_to_matrix(value)
|
|
396
712
|
self._matrix[index] = submatrix
|
|
@@ -401,90 +717,294 @@ class FourthOrderTensor:
|
|
|
401
717
|
else:
|
|
402
718
|
raise NotImplementedError('The r.h.s must be either an ndarray or an object of class {}'.format(self.__class__))
|
|
403
719
|
|
|
720
|
+
|
|
721
|
+
@classmethod
|
|
722
|
+
def identity(cls, **kwargs):
|
|
723
|
+
"""
|
|
724
|
+
Construct the Fourth-order identity tensor.
|
|
725
|
+
|
|
726
|
+
This is actually an alias for eye().
|
|
727
|
+
|
|
728
|
+
Parameters
|
|
729
|
+
----------
|
|
730
|
+
kwargs
|
|
731
|
+
Keyword arguments passed to the Fourth-order tensor constructor.
|
|
732
|
+
|
|
733
|
+
Returns
|
|
734
|
+
-------
|
|
735
|
+
FourthOrderTensor
|
|
736
|
+
|
|
737
|
+
See Also
|
|
738
|
+
--------
|
|
739
|
+
eye : Fourth-order identity tensor
|
|
740
|
+
"""
|
|
741
|
+
return cls.eye(**kwargs)
|
|
742
|
+
|
|
404
743
|
@classmethod
|
|
405
|
-
def
|
|
744
|
+
def _broadcast_matrix(cls, M, shape=None, **kwargs):
|
|
745
|
+
if shape is None:
|
|
746
|
+
new_shape = M.shape
|
|
747
|
+
elif isinstance(shape, int):
|
|
748
|
+
new_shape = (shape,) + M.shape
|
|
749
|
+
else:
|
|
750
|
+
new_shape = shape + M.shape
|
|
751
|
+
M_repeat = np.broadcast_to(M, new_shape)
|
|
752
|
+
t = cls(M_repeat, **kwargs)
|
|
753
|
+
t._matrix = t._matrix * t.mapping.matrix / kelvin_mapping.matrix
|
|
754
|
+
return t
|
|
755
|
+
|
|
756
|
+
@classmethod
|
|
757
|
+
def eye(cls, shape=(), **kwargs):
|
|
406
758
|
"""
|
|
407
|
-
Create a 4th-order identity tensor
|
|
759
|
+
Create a 4th-order identity tensor.
|
|
760
|
+
|
|
761
|
+
See notes for definition.
|
|
408
762
|
|
|
409
763
|
Parameters
|
|
410
764
|
----------
|
|
411
765
|
shape : int or tuple, optional
|
|
412
766
|
Shape of the tensor to create
|
|
413
|
-
|
|
414
|
-
If True, return the full tensor as a (3,3,3,3) or a (...,3,3,3,3) array. Otherwise, the tensor is returned
|
|
415
|
-
as a SymmetricTensor object.
|
|
416
|
-
mapping : str, optional
|
|
767
|
+
mapping : Kelvin mapping, optional
|
|
417
768
|
Mapping convention to use. Must be either Kelvin or Voigt.
|
|
418
769
|
|
|
419
770
|
Returns
|
|
420
771
|
-------
|
|
421
|
-
|
|
772
|
+
FourthOrderTensor or SymmetricFourthOrderTensor
|
|
422
773
|
Identity tensor
|
|
774
|
+
|
|
775
|
+
Notes
|
|
776
|
+
-----
|
|
777
|
+
|
|
778
|
+
The Fourth-order identity tensor is defined as:
|
|
779
|
+
|
|
780
|
+
.. math::
|
|
781
|
+
|
|
782
|
+
I_{ijkl} = \\frac12\\left( \\delta_{ik}\\delta_{jl} + \\delta_{il}\\delta_{jk}\\right)
|
|
783
|
+
|
|
784
|
+
Examples
|
|
785
|
+
--------
|
|
786
|
+
Create a (single) identity tensor:
|
|
787
|
+
|
|
788
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
789
|
+
>>> I = FourthOrderTensor.eye()
|
|
790
|
+
>>> print(I)
|
|
791
|
+
4th-order tensor (in Kelvin mapping):
|
|
792
|
+
[[1. 0. 0. 0. 0. 0.]
|
|
793
|
+
[0. 1. 0. 0. 0. 0.]
|
|
794
|
+
[0. 0. 1. 0. 0. 0.]
|
|
795
|
+
[0. 0. 0. 1. 0. 0.]
|
|
796
|
+
[0. 0. 0. 0. 1. 0.]
|
|
797
|
+
[0. 0. 0. 0. 0. 1.]]
|
|
798
|
+
|
|
799
|
+
Alternatively, one can use another mapping convention, e.g. Voigt:
|
|
800
|
+
|
|
801
|
+
>>> from Elasticipy.tensors.mapping import VoigtMapping
|
|
802
|
+
>>> Iv = FourthOrderTensor.eye(mapping=VoigtMapping())
|
|
803
|
+
>>> print(Iv)
|
|
804
|
+
4th-order tensor (in Voigt mapping):
|
|
805
|
+
[[1. 0. 0. 0. 0. 0. ]
|
|
806
|
+
[0. 1. 0. 0. 0. 0. ]
|
|
807
|
+
[0. 0. 1. 0. 0. 0. ]
|
|
808
|
+
[0. 0. 0. 0.5 0. 0. ]
|
|
809
|
+
[0. 0. 0. 0. 0.5 0. ]
|
|
810
|
+
[0. 0. 0. 0. 0. 0.5]]
|
|
811
|
+
|
|
812
|
+
Still, we have:
|
|
813
|
+
|
|
814
|
+
>>> I == Iv
|
|
815
|
+
True
|
|
816
|
+
|
|
817
|
+
as they correspond to the same tensor, but expressed as a matrix with different mapping conventions. Indeed,
|
|
818
|
+
one can check that:
|
|
819
|
+
|
|
820
|
+
>>> import numpy as np
|
|
821
|
+
>>> np.array_equal(I.full_tensor, Iv.full_tensor)
|
|
822
|
+
True
|
|
423
823
|
"""
|
|
424
|
-
|
|
425
|
-
if isinstance(shape, int):
|
|
426
|
-
shape = (shape,)
|
|
427
|
-
if len(shape):
|
|
428
|
-
for n in np.flip(shape):
|
|
429
|
-
eye = np.repeat(eye[np.newaxis,...], n, axis=0)
|
|
430
|
-
a = np.einsum('...ik,...jl->...ijkl', eye, eye)
|
|
431
|
-
b = np.einsum('...il,...jk->...ijkl', eye, eye)
|
|
432
|
-
full = 0.5*(a + b)
|
|
433
|
-
if return_full_tensor:
|
|
434
|
-
return full
|
|
435
|
-
else:
|
|
436
|
-
return cls(full, mapping=mapping)
|
|
824
|
+
return cls._broadcast_matrix(np.eye(6), shape=shape, **kwargs)
|
|
437
825
|
|
|
438
826
|
@classmethod
|
|
439
|
-
def
|
|
827
|
+
def ones(cls, shape=None, **kwargs):
|
|
440
828
|
"""
|
|
441
|
-
|
|
829
|
+
Create a 4th-order tensor full of ones.
|
|
442
830
|
|
|
443
831
|
Parameters
|
|
444
832
|
----------
|
|
445
|
-
shape :
|
|
833
|
+
shape : int or tuple, optional
|
|
446
834
|
Shape of the tensor to create
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
mapping : str, optional
|
|
450
|
-
Mapping convention to use. Must be either Kelvin or Voigt.
|
|
835
|
+
kwargs
|
|
836
|
+
keyword arguments passed to the constructor
|
|
451
837
|
|
|
452
838
|
Returns
|
|
453
839
|
-------
|
|
454
|
-
FourthOrderTensor
|
|
840
|
+
FourthOrderTensor
|
|
841
|
+
|
|
842
|
+
Examples
|
|
843
|
+
--------
|
|
844
|
+
>>> tensor_of_ones = FourthOrderTensor.ones()
|
|
845
|
+
>>> tensor_of_ones
|
|
846
|
+
4th-order tensor (in Kelvin mapping):
|
|
847
|
+
[[1. 1. 1. 1.41421356 1.41421356 1.41421356]
|
|
848
|
+
[1. 1. 1. 1.41421356 1.41421356 1.41421356]
|
|
849
|
+
[1. 1. 1. 1.41421356 1.41421356 1.41421356]
|
|
850
|
+
[1.41421356 1.41421356 1.41421356 2. 2. 2. ]
|
|
851
|
+
[1.41421356 1.41421356 1.41421356 2. 2. 2. ]
|
|
852
|
+
[1.41421356 1.41421356 1.41421356 2. 2. 2. ]]
|
|
853
|
+
|
|
854
|
+
At first sight, the tensor may appear not full of ones at all, but the representation above uses the Kelvin
|
|
855
|
+
mapping convention. Indeed, one can check that the full tensor is actually full of ones. E.g.:
|
|
856
|
+
|
|
857
|
+
>>> tensor_of_ones.full_tensor[0,1,0,2]
|
|
858
|
+
1.0
|
|
859
|
+
|
|
860
|
+
Alternatively, the Voigt mapping convention may help figuring it out:
|
|
861
|
+
|
|
862
|
+
>>> from Elasticipy.tensors.mapping import VoigtMapping
|
|
863
|
+
>>> tensor_of_ones_voigt = FourthOrderTensor.ones(mapping=VoigtMapping())
|
|
864
|
+
>>> tensor_of_ones_voigt
|
|
865
|
+
4th-order tensor (in Voigt mapping):
|
|
866
|
+
[[1. 1. 1. 1. 1. 1.]
|
|
867
|
+
[1. 1. 1. 1. 1. 1.]
|
|
868
|
+
[1. 1. 1. 1. 1. 1.]
|
|
869
|
+
[1. 1. 1. 1. 1. 1.]
|
|
870
|
+
[1. 1. 1. 1. 1. 1.]
|
|
871
|
+
[1. 1. 1. 1. 1. 1.]]
|
|
872
|
+
|
|
873
|
+
although both tensors are actually the same:
|
|
874
|
+
|
|
875
|
+
>>> tensor_of_ones == tensor_of_ones_voigt
|
|
876
|
+
True
|
|
455
877
|
"""
|
|
456
|
-
|
|
457
|
-
if isinstance(shape, int):
|
|
458
|
-
shape = (shape,)
|
|
459
|
-
if len(shape):
|
|
460
|
-
for n in np.flip(shape):
|
|
461
|
-
eye = np.repeat(eye[np.newaxis,...], n, axis=0)
|
|
462
|
-
J = np.einsum('...ij,...kl->...ijkl',eye, eye)/3
|
|
463
|
-
if return_full_tensor:
|
|
464
|
-
return J
|
|
465
|
-
else:
|
|
466
|
-
return FourthOrderTensor(J, mapping=mapping)
|
|
878
|
+
return cls._broadcast_matrix(kelvin_mapping.matrix, shape=shape, **kwargs)
|
|
467
879
|
|
|
468
880
|
@classmethod
|
|
469
|
-
def
|
|
881
|
+
def identity_spherical_part(cls, shape=(), **kwargs):
|
|
470
882
|
"""
|
|
471
|
-
Return the
|
|
883
|
+
Return the spherical part of the identity tensor.
|
|
884
|
+
|
|
885
|
+
See Notes for mathematical definition.
|
|
472
886
|
|
|
473
887
|
Parameters
|
|
474
888
|
----------
|
|
475
889
|
shape : tuple of int, optional
|
|
476
890
|
Shape of the tensor to create
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
891
|
+
kwargs
|
|
892
|
+
Keyword arguments passed to the Fourth-order tensor constructor.
|
|
893
|
+
|
|
894
|
+
Returns
|
|
895
|
+
-------
|
|
896
|
+
FourthOrderTensor
|
|
897
|
+
|
|
898
|
+
See Also
|
|
899
|
+
--------
|
|
900
|
+
identity_tensor : return the identity tensor
|
|
901
|
+
identity_deviatoric_part : return the deviatoric part of the identity tensor
|
|
902
|
+
|
|
903
|
+
Notes
|
|
904
|
+
-----
|
|
905
|
+
The spherical part of the identity tensor is defined as:
|
|
906
|
+
|
|
907
|
+
.. math::
|
|
908
|
+
|
|
909
|
+
J_{ijkl} = \\frac13 \\delta_{ij}\\delta_{kl}
|
|
910
|
+
|
|
911
|
+
Examples
|
|
912
|
+
--------
|
|
913
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
914
|
+
>>> J = FourthOrderTensor.identity_spherical_part()
|
|
915
|
+
>>> print(J)
|
|
916
|
+
4th-order tensor (in Kelvin mapping):
|
|
917
|
+
[[0.33333333 0.33333333 0.33333333 0. 0. 0. ]
|
|
918
|
+
[0.33333333 0.33333333 0.33333333 0. 0. 0. ]
|
|
919
|
+
[0.33333333 0.33333333 0.33333333 0. 0. 0. ]
|
|
920
|
+
[0. 0. 0. 0. 0. 0. ]
|
|
921
|
+
[0. 0. 0. 0. 0. 0. ]
|
|
922
|
+
[0. 0. 0. 0. 0. 0. ]]
|
|
923
|
+
|
|
924
|
+
On can check that J has zero deviatoric part:
|
|
925
|
+
|
|
926
|
+
>>> J.deviatoric_part()
|
|
927
|
+
4th-order tensor (in Kelvin mapping):
|
|
928
|
+
[[2.77555756e-17 2.77555756e-17 2.77555756e-17 0.00000000e+00
|
|
929
|
+
0.00000000e+00 0.00000000e+00]
|
|
930
|
+
[2.77555756e-17 2.77555756e-17 2.77555756e-17 0.00000000e+00
|
|
931
|
+
0.00000000e+00 0.00000000e+00]
|
|
932
|
+
[2.77555756e-17 2.77555756e-17 2.77555756e-17 0.00000000e+00
|
|
933
|
+
0.00000000e+00 0.00000000e+00]
|
|
934
|
+
[0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
|
|
935
|
+
0.00000000e+00 0.00000000e+00]
|
|
936
|
+
[0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
|
|
937
|
+
0.00000000e+00 0.00000000e+00]
|
|
938
|
+
[0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
|
|
939
|
+
0.00000000e+00 0.00000000e+00]]
|
|
940
|
+
"""
|
|
941
|
+
A = np.zeros((6, 6))
|
|
942
|
+
A[:3, :3] = 1 / 3
|
|
943
|
+
return cls._broadcast_matrix(A, shape=shape, **kwargs)
|
|
944
|
+
|
|
945
|
+
@classmethod
|
|
946
|
+
def identity_deviatoric_part(cls, **kwargs):
|
|
947
|
+
"""
|
|
948
|
+
Return the deviatoric part of the identity tensor.
|
|
949
|
+
|
|
950
|
+
See notes for the mathematical definition.
|
|
951
|
+
|
|
952
|
+
Parameters
|
|
953
|
+
----------
|
|
954
|
+
kwargs
|
|
955
|
+
keyword arguments passed to eye constructor
|
|
481
956
|
|
|
482
957
|
Returns
|
|
483
958
|
-------
|
|
484
959
|
FourthOrderTensor or SymmetricTensor
|
|
960
|
+
|
|
961
|
+
See Also
|
|
962
|
+
--------
|
|
963
|
+
identity_tensor : return the identity tensor
|
|
964
|
+
identity_spherical_part : return the spherical part of the identity tensor
|
|
965
|
+
|
|
966
|
+
Notes
|
|
967
|
+
-----
|
|
968
|
+
The deviatoric part of the identity tensor is defined as:
|
|
969
|
+
|
|
970
|
+
.. math::
|
|
971
|
+
|
|
972
|
+
K = I - J
|
|
973
|
+
|
|
974
|
+
where :math:`I` and :math:`J` denote the identity and the deviatoric part of the identity tensor, respectively.
|
|
975
|
+
|
|
976
|
+
Examples
|
|
977
|
+
--------
|
|
978
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
979
|
+
>>> K = FourthOrderTensor.identity_deviatoric_part()
|
|
980
|
+
>>> print(K)
|
|
981
|
+
4th-order tensor (in Kelvin mapping):
|
|
982
|
+
[[ 0.66666667 -0.33333333 -0.33333333 0. 0. 0. ]
|
|
983
|
+
[-0.33333333 0.66666667 -0.33333333 0. 0. 0. ]
|
|
984
|
+
[-0.33333333 -0.33333333 0.66666667 0. 0. 0. ]
|
|
985
|
+
[ 0. 0. 0. 1. 0. 0. ]
|
|
986
|
+
[ 0. 0. 0. 0. 1. 0. ]
|
|
987
|
+
[ 0. 0. 0. 0. 0. 1. ]]
|
|
988
|
+
|
|
989
|
+
One can check that K has zero spherical part:
|
|
990
|
+
|
|
991
|
+
>>> print(K.spherical_part())
|
|
992
|
+
4th-order tensor (in Kelvin mapping):
|
|
993
|
+
[[2.77555756e-17 2.77555756e-17 2.77555756e-17 0.00000000e+00
|
|
994
|
+
0.00000000e+00 0.00000000e+00]
|
|
995
|
+
[2.77555756e-17 2.77555756e-17 2.77555756e-17 0.00000000e+00
|
|
996
|
+
0.00000000e+00 0.00000000e+00]
|
|
997
|
+
[2.77555756e-17 2.77555756e-17 2.77555756e-17 0.00000000e+00
|
|
998
|
+
0.00000000e+00 0.00000000e+00]
|
|
999
|
+
[0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
|
|
1000
|
+
0.00000000e+00 0.00000000e+00]
|
|
1001
|
+
[0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
|
|
1002
|
+
0.00000000e+00 0.00000000e+00]
|
|
1003
|
+
[0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
|
|
1004
|
+
0.00000000e+00 0.00000000e+00]]
|
|
485
1005
|
"""
|
|
486
|
-
I = FourthOrderTensor.identity(
|
|
487
|
-
J = FourthOrderTensor.identity_spherical_part(
|
|
1006
|
+
I = FourthOrderTensor.identity(**kwargs)
|
|
1007
|
+
J = FourthOrderTensor.identity_spherical_part(**kwargs)
|
|
488
1008
|
return I-J
|
|
489
1009
|
|
|
490
1010
|
def spherical_part(self):
|
|
@@ -495,6 +1015,11 @@ class FourthOrderTensor:
|
|
|
495
1015
|
-------
|
|
496
1016
|
FourthOrderTensor
|
|
497
1017
|
Spherical part of the tensor
|
|
1018
|
+
|
|
1019
|
+
See Also
|
|
1020
|
+
--------
|
|
1021
|
+
identity_tensor : return the identity tensor
|
|
1022
|
+
deviatoric_part : return the deviatoric part of the tensor
|
|
498
1023
|
"""
|
|
499
1024
|
I = self.identity_spherical_part(shape=self.shape)
|
|
500
1025
|
return I.ddot(self)
|
|
@@ -507,6 +1032,11 @@ class FourthOrderTensor:
|
|
|
507
1032
|
-------
|
|
508
1033
|
FourthOrderTensor
|
|
509
1034
|
Deviatoric part of the tensor
|
|
1035
|
+
|
|
1036
|
+
See Also
|
|
1037
|
+
--------
|
|
1038
|
+
identity_tensor : return the identity tensor
|
|
1039
|
+
spherical_part : return the spherical part of the tensor
|
|
510
1040
|
"""
|
|
511
1041
|
K = self.identity_deviatoric_part(shape=self.shape)
|
|
512
1042
|
return K.ddot(self)
|
|
@@ -519,12 +1049,72 @@ class FourthOrderTensor:
|
|
|
519
1049
|
-------
|
|
520
1050
|
FourthOrderTensor
|
|
521
1051
|
Inverse tensor
|
|
1052
|
+
|
|
1053
|
+
Examples
|
|
1054
|
+
--------
|
|
1055
|
+
Let consider a random Fourth-order tensor:
|
|
1056
|
+
|
|
1057
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
1058
|
+
>>> T = FourthOrderTensor.rand()
|
|
1059
|
+
>>> print(T) # doctest: +SKIP
|
|
1060
|
+
|
|
1061
|
+
>>> Tinv = T.inv()
|
|
1062
|
+
>>> print(Tinv) # doctest: +SKIP
|
|
1063
|
+
|
|
1064
|
+
One can check that ``T.ddot(Tinv)`` and ``Tinv.ddot(T)`` are really close to the identity tensor:
|
|
1065
|
+
|
|
1066
|
+
>>> I = FourthOrderTensor.eye()
|
|
1067
|
+
>>> (T.ddot(Tinv) - I) * 1e16 # doctest: +SKIP
|
|
1068
|
+
4th-order tensor (in Kelvin mapping):
|
|
1069
|
+
[[ 1.00000000e+00 0.00000000e+00 1.11022302e-16 0.00000000e+00
|
|
1070
|
+
3.14018492e-16 0.00000000e+00]
|
|
1071
|
+
[-2.22044605e-16 1.00000000e+00 2.77555756e-17 -1.25607397e-15
|
|
1072
|
+
2.35513869e-16 -7.85046229e-17]
|
|
1073
|
+
[ 1.55431223e-15 0.00000000e+00 1.00000000e+00 -3.14018492e-16
|
|
1074
|
+
0.00000000e+00 -4.71027738e-16]
|
|
1075
|
+
[-6.28036983e-16 -4.39625888e-15 0.00000000e+00 1.00000000e+00
|
|
1076
|
+
0.00000000e+00 1.11022302e-16]
|
|
1077
|
+
[-1.25607397e-15 -4.39625888e-15 0.00000000e+00 2.55351296e-15
|
|
1078
|
+
1.00000000e+00 -1.66533454e-16]
|
|
1079
|
+
[-5.88784672e-16 -1.17756934e-15 -2.62499833e-16 5.96744876e-16
|
|
1080
|
+
1.24900090e-16 1.00000000e+00]]
|
|
1081
|
+
|
|
1082
|
+
>>> (Tinv.ddot(T) - I) * 1e16 # doctest: +SKIP
|
|
1083
|
+
4th-order tensor (in Kelvin mapping):
|
|
1084
|
+
[[ 1.00000000e+00 -1.33226763e-15 -3.99680289e-15 -6.90840682e-15
|
|
1085
|
+
-7.53644380e-15 -2.51214793e-15]
|
|
1086
|
+
[ 2.33146835e-15 1.00000000e+00 1.55431223e-15 -7.85046229e-16
|
|
1087
|
+
1.41308321e-15 9.42055475e-16]
|
|
1088
|
+
[ 3.88578059e-16 1.11022302e-16 1.00000000e+00 -3.92523115e-16
|
|
1089
|
+
-1.57009246e-16 1.57009246e-16]
|
|
1090
|
+
[-5.88784672e-17 -1.86448479e-16 -1.47196168e-16 1.00000000e+00
|
|
1091
|
+
-2.49800181e-16 -2.08166817e-16]
|
|
1092
|
+
[ 5.10280049e-16 7.85046229e-17 -7.85046229e-17 1.27675648e-15
|
|
1093
|
+
1.00000000e+00 7.77156117e-16]
|
|
1094
|
+
[-7.85046229e-16 -6.28036983e-16 6.28036983e-16 2.44249065e-15
|
|
1095
|
+
3.55271368e-15 1.00000000e+00]]
|
|
1096
|
+
|
|
1097
|
+
This function obvisouly also works for tensor arrays. E.g.:
|
|
1098
|
+
|
|
1099
|
+
>>> T = FourthOrderTensor.rand(shape=(5,3))
|
|
1100
|
+
>>> Tinv = T.inv()
|
|
1101
|
+
>>> Tinv.shape
|
|
1102
|
+
(5, 3)
|
|
1103
|
+
|
|
1104
|
+
Again, one can check that ``T.ddot(Tinv)`` is close to the array of identity tensors:
|
|
1105
|
+
|
|
1106
|
+
>>> import numpy as np
|
|
1107
|
+
>>> I = FourthOrderTensor.eye(shape=(5,3))
|
|
1108
|
+
>>> np.max(T.ddot(Tinv).matrix() - I.matrix()) # doctest: +SKIP
|
|
1109
|
+
5.906386491005833e-14
|
|
522
1110
|
"""
|
|
523
1111
|
matrix_inv = np.linalg.inv(self._matrix)
|
|
524
|
-
|
|
1112
|
+
t = self.__class__(matrix_inv, mapping=kelvin_mapping)
|
|
1113
|
+
t.mapping = self.mapping.mapping_inverse
|
|
1114
|
+
return t
|
|
525
1115
|
|
|
526
1116
|
@classmethod
|
|
527
|
-
def zeros(cls, shape=()):
|
|
1117
|
+
def zeros(cls, shape=(), **kwargs):
|
|
528
1118
|
"""
|
|
529
1119
|
Create a fourth-order tensor populated with zeros
|
|
530
1120
|
|
|
@@ -532,45 +1122,138 @@ class FourthOrderTensor:
|
|
|
532
1122
|
----------
|
|
533
1123
|
shape : int or tuple, optional
|
|
534
1124
|
Shape of the tensor to create
|
|
1125
|
+
kwargs
|
|
1126
|
+
Keyword arguments passed to the FourthOrderTensor constructor
|
|
1127
|
+
|
|
535
1128
|
Returns
|
|
536
1129
|
-------
|
|
537
1130
|
FourthOrderTensor
|
|
1131
|
+
|
|
1132
|
+
Examples
|
|
1133
|
+
--------
|
|
1134
|
+
The single-valued null 4th order tensor is just:
|
|
1135
|
+
|
|
1136
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
1137
|
+
>>> FourthOrderTensor.zeros()
|
|
1138
|
+
4th-order tensor (in Kelvin mapping):
|
|
1139
|
+
[[0. 0. 0. 0. 0. 0.]
|
|
1140
|
+
[0. 0. 0. 0. 0. 0.]
|
|
1141
|
+
[0. 0. 0. 0. 0. 0.]
|
|
1142
|
+
[0. 0. 0. 0. 0. 0.]
|
|
1143
|
+
[0. 0. 0. 0. 0. 0.]
|
|
1144
|
+
[0. 0. 0. 0. 0. 0.]]
|
|
1145
|
+
|
|
1146
|
+
One can also create an array of such tensors:
|
|
1147
|
+
|
|
1148
|
+
>>> zeros_tensor = FourthOrderTensor.zeros(shape=3)
|
|
1149
|
+
|
|
1150
|
+
and check that it populated with zeros:
|
|
1151
|
+
|
|
1152
|
+
>>> zeros_tensor == 0.
|
|
1153
|
+
array([ True, True, True])
|
|
538
1154
|
"""
|
|
539
1155
|
if isinstance(shape, int):
|
|
540
1156
|
shape = (shape, 6, 6)
|
|
541
1157
|
else:
|
|
542
1158
|
shape = shape + (6,6)
|
|
543
1159
|
zeros = np.zeros(shape)
|
|
544
|
-
return cls(zeros)
|
|
1160
|
+
return cls(zeros, **kwargs)
|
|
545
1161
|
|
|
546
1162
|
def matrix(self, mapping_convention=None):
|
|
1163
|
+
"""
|
|
1164
|
+
Returns the components of the tensor as a matrix.
|
|
1165
|
+
|
|
1166
|
+
Parameters
|
|
1167
|
+
----------
|
|
1168
|
+
mapping_convention : VoigtMapping, optional
|
|
1169
|
+
Mapping convention to use for the returned matrix. If not provided, that of the tensor is used.
|
|
1170
|
+
|
|
1171
|
+
Returns
|
|
1172
|
+
-------
|
|
1173
|
+
numpy.ndarray
|
|
1174
|
+
Components of the tensor as a matrix
|
|
1175
|
+
|
|
1176
|
+
Examples
|
|
1177
|
+
--------
|
|
1178
|
+
Create an identity 4th-order tensor:
|
|
1179
|
+
|
|
1180
|
+
>>> from Elasticipy.tensors.fourth_order import FourthOrderTensor
|
|
1181
|
+
>>> t = FourthOrderTensor.eye()
|
|
1182
|
+
|
|
1183
|
+
Its matrix with respect to Kelvin mapping is:
|
|
1184
|
+
|
|
1185
|
+
>>> t.matrix()
|
|
1186
|
+
array([[1., 0., 0., 0., 0., 0.],
|
|
1187
|
+
[0., 1., 0., 0., 0., 0.],
|
|
1188
|
+
[0., 0., 1., 0., 0., 0.],
|
|
1189
|
+
[0., 0., 0., 1., 0., 0.],
|
|
1190
|
+
[0., 0., 0., 0., 1., 0.],
|
|
1191
|
+
[0., 0., 0., 0., 0., 1.]])
|
|
1192
|
+
|
|
1193
|
+
whereas, when using the Voigt mapping, we have:
|
|
1194
|
+
|
|
1195
|
+
>>> from Elasticipy.tensors.mapping import VoigtMapping
|
|
1196
|
+
>>> t.matrix(mapping_convention=VoigtMapping())
|
|
1197
|
+
array([[1. , 0. , 0. , 0. , 0. , 0. ],
|
|
1198
|
+
[0. , 1. , 0. , 0. , 0. , 0. ],
|
|
1199
|
+
[0. , 0. , 1. , 0. , 0. , 0. ],
|
|
1200
|
+
[0. , 0. , 0. , 0.5, 0. , 0. ],
|
|
1201
|
+
[0. , 0. , 0. , 0. , 0.5, 0. ],
|
|
1202
|
+
[0. , 0. , 0. , 0. , 0. , 0.5]])
|
|
1203
|
+
|
|
1204
|
+
For stiffness tensors, the default mapping convention is Voigt, so that:
|
|
1205
|
+
|
|
1206
|
+
>>> from Elasticipy.tensors.elasticity import StiffnessTensor, ComplianceTensor
|
|
1207
|
+
>>> StiffnessTensor.eye().matrix()
|
|
1208
|
+
array([[1. , 0. , 0. , 0. , 0. , 0. ],
|
|
1209
|
+
[0. , 1. , 0. , 0. , 0. , 0. ],
|
|
1210
|
+
[0. , 0. , 1. , 0. , 0. , 0. ],
|
|
1211
|
+
[0. , 0. , 0. , 0.5, 0. , 0. ],
|
|
1212
|
+
[0. , 0. , 0. , 0. , 0.5, 0. ],
|
|
1213
|
+
[0. , 0. , 0. , 0. , 0. , 0.5]])
|
|
1214
|
+
|
|
1215
|
+
whereas for compliance tensor, the default mapping convention gives:
|
|
1216
|
+
|
|
1217
|
+
>>> ComplianceTensor.eye().matrix()
|
|
1218
|
+
array([[1., 0., 0., 0., 0., 0.],
|
|
1219
|
+
[0., 1., 0., 0., 0., 0.],
|
|
1220
|
+
[0., 0., 1., 0., 0., 0.],
|
|
1221
|
+
[0., 0., 0., 2., 0., 0.],
|
|
1222
|
+
[0., 0., 0., 0., 2., 0.],
|
|
1223
|
+
[0., 0., 0., 0., 0., 2.]])
|
|
1224
|
+
"""
|
|
547
1225
|
matrix = self._matrix
|
|
548
1226
|
if mapping_convention is None:
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
1227
|
+
mapping_convention = self.mapping
|
|
1228
|
+
elif isinstance(mapping_convention, str):
|
|
1229
|
+
if mapping_convention.lower() == 'voigt':
|
|
1230
|
+
mapping_convention = VoigtMapping()
|
|
1231
|
+
elif mapping_convention.lower() == 'kelvin':
|
|
1232
|
+
mapping_convention = kelvin_mapping
|
|
1233
|
+
else:
|
|
1234
|
+
raise ValueError('Mapping convention must be either Kelvin or Voigt')
|
|
1235
|
+
return matrix / kelvin_mapping.matrix * mapping_convention.matrix
|
|
1236
|
+
|
|
1237
|
+
def copy(self):
|
|
1238
|
+
"""Create a copy of the tensor"""
|
|
1239
|
+
a = deepcopy(self)
|
|
1240
|
+
return a
|
|
559
1241
|
|
|
560
1242
|
class SymmetricFourthOrderTensor(FourthOrderTensor):
|
|
561
|
-
|
|
1243
|
+
_tensor_name = 'Symmetric 4th-order'
|
|
562
1244
|
|
|
563
1245
|
def __init__(self, M, check_symmetries=True, force_symmetries=False, **kwargs):
|
|
564
1246
|
"""
|
|
565
|
-
Construct a fully symmetric fourth-order tensor from a (...,6,6) or a (3,3,3,3) array.
|
|
1247
|
+
Construct a fully symmetric fourth-order tensor from a (...,6,6) or a (...,3,3,3,3) array.
|
|
566
1248
|
|
|
567
|
-
The input matrix must be symmetric, otherwise an error is thrown (except if check_symmetry==False
|
|
1249
|
+
The input matrix must be symmetric, otherwise an error is thrown (except if ``check_symmetry==False``, see
|
|
1250
|
+
below)
|
|
568
1251
|
|
|
569
1252
|
Parameters
|
|
570
1253
|
----------
|
|
571
|
-
M : np.ndarray
|
|
572
|
-
(6,6) matrix corresponding to the stiffness tensor,
|
|
573
|
-
(3,3,3,3).
|
|
1254
|
+
M : np.ndarray or FourthOrderTensor
|
|
1255
|
+
(6,6) matrix corresponding to the stiffness tensor, or slices of (6,6) matrices or array of shape
|
|
1256
|
+
(...,3,3,3,3).
|
|
574
1257
|
check_symmetries : bool, optional
|
|
575
1258
|
Whether to check or not that the tensor to built displays both major and minor symmetries (see Notes).
|
|
576
1259
|
force_symmetries : bool, optional
|
|
@@ -596,27 +1279,27 @@ class SymmetricFourthOrderTensor(FourthOrderTensor):
|
|
|
596
1279
|
elif check_symmetries and not np.all(np.isclose(self._matrix, self._matrix.swapaxes(-1, -2))):
|
|
597
1280
|
raise ValueError('The input matrix must be symmetric')
|
|
598
1281
|
|
|
599
|
-
def
|
|
1282
|
+
def linear_invariants(self):
|
|
600
1283
|
"""
|
|
601
|
-
Compute the invariants of the tensor.
|
|
1284
|
+
Compute the linear invariants of the tensor, or tensor array.
|
|
602
1285
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
Parameters
|
|
606
|
-
----------
|
|
607
|
-
order : str, optional
|
|
608
|
-
If 'linear', only A1 and A2 are returned
|
|
609
|
-
If 'quadratic', A1², A2², A1*A2, B1, B2, B3, B4 and B5 are returned
|
|
610
|
-
If 'all' (default), A1, A2, A1², A2², B1, B2, B3, B4 and B5 are returned
|
|
1286
|
+
If the object is a tensor array, the linear invariants are returned as arrays of each invariant. See notes for
|
|
1287
|
+
the actual definitions.
|
|
611
1288
|
|
|
612
1289
|
Returns
|
|
613
1290
|
-------
|
|
614
|
-
|
|
615
|
-
|
|
1291
|
+
A1 : float or np.ndarray
|
|
1292
|
+
First linear invariant
|
|
1293
|
+
A2 : float or np.ndarray
|
|
1294
|
+
Second linear invariant
|
|
1295
|
+
|
|
1296
|
+
See Also
|
|
1297
|
+
--------
|
|
1298
|
+
quadratic_invariants : compute the quadratic invariants of a fourth-order tensor
|
|
616
1299
|
|
|
617
1300
|
Notes
|
|
618
1301
|
-----
|
|
619
|
-
The
|
|
1302
|
+
The linear invariants are:
|
|
620
1303
|
|
|
621
1304
|
.. math::
|
|
622
1305
|
|
|
@@ -624,7 +1307,29 @@ class SymmetricFourthOrderTensor(FourthOrderTensor):
|
|
|
624
1307
|
|
|
625
1308
|
A_2=C_{iijj}
|
|
626
1309
|
|
|
627
|
-
|
|
1310
|
+
"""
|
|
1311
|
+
t = self.full_tensor
|
|
1312
|
+
A1 = np.einsum('...ijij->...',t)
|
|
1313
|
+
A2 = np.einsum('...iijj->...',t)
|
|
1314
|
+
return A1, A2
|
|
1315
|
+
|
|
1316
|
+
def quadratic_invariants(self):
|
|
1317
|
+
"""
|
|
1318
|
+
Compute the quadratic invariants of the tensor, or tensor array.
|
|
1319
|
+
|
|
1320
|
+
If the object is a tensor array, the returned values are arrays of each invariant. See notes for definitions.
|
|
1321
|
+
|
|
1322
|
+
Returns
|
|
1323
|
+
-------
|
|
1324
|
+
B1, B2, B3, B4, B5 : float or np.ndarray
|
|
1325
|
+
|
|
1326
|
+
See Also
|
|
1327
|
+
--------
|
|
1328
|
+
linear_invariants : compute the linear invariants of a Fourth-order tensor
|
|
1329
|
+
|
|
1330
|
+
Notes
|
|
1331
|
+
-----
|
|
1332
|
+
The quadratic invariants are defined as [Norris]_:
|
|
628
1333
|
|
|
629
1334
|
.. math::
|
|
630
1335
|
|
|
@@ -640,23 +1345,41 @@ class SymmetricFourthOrderTensor(FourthOrderTensor):
|
|
|
640
1345
|
|
|
641
1346
|
References
|
|
642
1347
|
----------
|
|
643
|
-
.. [
|
|
1348
|
+
.. [Norris] Norris, A. N. (22 May 2007). "Quadratic invariants of elastic moduli". The Quarterly Journal of Mechanics
|
|
644
1349
|
and Applied Mathematics. 60 (3): 367–389. doi:10.1093/qjmam/hbm007
|
|
1350
|
+
|
|
645
1351
|
"""
|
|
646
|
-
t = self.full_tensor
|
|
647
|
-
|
|
648
|
-
A1 = np.einsum('...ijij->...',t)
|
|
649
|
-
A2 = np.einsum('...iijj->...',t)
|
|
650
|
-
lin_inv = (A1, A2)
|
|
651
|
-
if order == 'linear':
|
|
652
|
-
return lin_inv
|
|
653
|
-
B1 = np.einsum('...ijkl,...ijkl->...',t, t)
|
|
1352
|
+
t = self.full_tensor
|
|
1353
|
+
B1 = np.einsum('...ijkl,...ijkl->...', t, t)
|
|
654
1354
|
B2 = np.einsum('...iikl,...jjkl->...', t, t)
|
|
655
1355
|
B3 = np.einsum('...iikl,...jkjl->...', t, t)
|
|
656
1356
|
B4 = np.einsum('...kiil,...kjjl->...', t, t)
|
|
657
1357
|
B5 = np.einsum('...ijkl,...ikjl->...', t, t)
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1358
|
+
return B1, B2, B3, B4, B5
|
|
1359
|
+
|
|
1360
|
+
def infinite_random_average(self):
|
|
1361
|
+
"""
|
|
1362
|
+
Compute the average of the tensor, assuming that an infinite number of random orientations is applied.
|
|
1363
|
+
|
|
1364
|
+
Returns
|
|
1365
|
+
-------
|
|
1366
|
+
FourthOrderTensor
|
|
1367
|
+
Average tensor or tensor array. The returned array will be of the same shape as the input object.
|
|
1368
|
+
"""
|
|
1369
|
+
new_tensor = deepcopy(self)
|
|
1370
|
+
matrix = self._matrix / kelvin_mapping.matrix
|
|
1371
|
+
A = matrix[..., 0, 0] + matrix[..., 1, 1] + matrix[..., 2, 2]
|
|
1372
|
+
B = matrix[..., 0, 1] + matrix[..., 0, 2] + matrix[..., 1, 2]
|
|
1373
|
+
C = matrix[..., 3, 3] + matrix[..., 4, 4] + matrix[..., 5, 5]
|
|
1374
|
+
C11 = 1/5 * A + 2/15 * B + 4/15 * C
|
|
1375
|
+
C12 = 1/15 * A + 4/15 * B - 2/15 * C
|
|
1376
|
+
C44 = 1/15 * A - 1/15 * B + 1/5 * C
|
|
1377
|
+
new_matrix = _isotropic_matrix(C11, C12, C44)
|
|
1378
|
+
new_tensor._matrix = new_matrix * kelvin_mapping.matrix
|
|
1379
|
+
return new_tensor
|
|
1380
|
+
|
|
1381
|
+
@classmethod
|
|
1382
|
+
def rand(cls, shape=None, **kwargs):
|
|
1383
|
+
t1 = super().rand(shape)
|
|
1384
|
+
return cls(t1, force_symmetries=True, **kwargs)
|
|
1385
|
+
|