pydrex.tensors

PyDRex: Tensor operation functions and helpers.

For Voigt notation, the symmetric 6x6 matrix representation is used, which assumes that the fourth order tensor being represented as such is also symmetric. The vectorial notation uses 21 components which are the independent components of the symmetric 6x6 matrix.

  1"""> PyDRex: Tensor operation functions and helpers.
  2
  3For Voigt notation, the symmetric 6x6 matrix representation is used,
  4which assumes that the fourth order tensor being represented as such is also symmetric.
  5The vectorial notation uses 21 components which are the independent components of the
  6symmetric 6x6 matrix.
  7
  8"""
  9
 10import numba as nb
 11import numpy as np
 12
 13
 14@nb.njit(fastmath=True)
 15def polar_decompose(matrix, left=True):
 16    """Compute polar decomposition of M as either M = RU (right) or M = VP (left)."""
 17    U, S, Vh = np.linalg.svd(matrix)
 18    if left:
 19        return U @ Vh, U @ (np.diag(S) @ U.transpose())
 20    U_matrix = Vh.transpose() @ (np.diag(S) @ Vh)
 21    return matrix @ np.linalg.inv(U_matrix), U_matrix
 22
 23
 24@nb.njit(fastmath=True)
 25def invariants_second_order(tensor):
 26    """Calculate invariants of a second order tensor."""
 27    return (
 28        np.trace(tensor),
 29        tensor[0, 0] * tensor[1, 1]
 30        + tensor[1, 1] * tensor[2, 2]
 31        + tensor[2, 2] * tensor[0, 0]
 32        - tensor[0, 1] * tensor[1, 0]
 33        - tensor[1, 2] * tensor[2, 1]
 34        - tensor[2, 0] * tensor[0, 2],
 35        np.linalg.det(tensor),
 36    )
 37
 38
 39@nb.njit(fastmath=True)
 40def voigt_decompose(matrix: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
 41    """Decompose elastic tensor (as 6x6 Voigt matrix) into distinct contractions.
 42
 43    Return the only two independent contractions of the elastic tensor given as a 6x6
 44    Voigt `matrix`. With reference to the full 4-th order elastic tensor, the
 45    contractions are defined as:
 46    - $d_{ij} = C_{ijkk}$ (dilatational stiffness tensor)
 47    - $v_{ij} = C_{ijkj}$ (deviatoric stiffness tensor)
 48
 49    Any vector which is an eigenvector of both $d_{ij}$ and $v_{ij}$ is always normal to
 50    a symmetry plane of the elastic medium.
 51
 52    See Equations 3.4 & 3.5 in [Browaeys & Chevrot (2004)](https://doi.org/10.1111/j.1365-246X.2004.02415.x).
 53
 54    """
 55    # 1. Compose dᵢⱼ = Cᵢⱼₖₖ (dilatational stiffness tensor)
 56    # Eq. 3.4 in Browaeys & Chevrot, 2004.
 57    stiffness_dilat = np.empty((3, 3))
 58    for i in range(3):
 59        stiffness_dilat[i, i] = matrix[:3, i].sum()
 60    stiffness_dilat[0, 1] = stiffness_dilat[1, 0] = matrix[:3, 5].sum()
 61    stiffness_dilat[0, 2] = stiffness_dilat[2, 0] = matrix[:3, 4].sum()
 62    stiffness_dilat[1, 2] = stiffness_dilat[2, 1] = matrix[:3, 3].sum()
 63    # 2. Compose vᵢⱼ = Cᵢⱼₖⱼ (deviatoric stiffness tensor)
 64    # Eq. 3.5, Browaeys & Chevrot, 2004.
 65    stiffness_deviat = np.empty((3, 3))
 66    stiffness_deviat[0, 0] = matrix[0, 0] + matrix[4, 4] + matrix[5, 5]
 67    stiffness_deviat[1, 1] = matrix[1, 1] + matrix[3, 3] + matrix[5, 5]
 68    stiffness_deviat[2, 2] = matrix[2, 2] + matrix[3, 3] + matrix[4, 4]
 69    stiffness_deviat[0, 1] = matrix[0, 5] + matrix[1, 5] + matrix[3, 4]
 70    stiffness_deviat[0, 2] = matrix[0, 4] + matrix[2, 4] + matrix[3, 5]
 71    stiffness_deviat[1, 2] = matrix[1, 3] + matrix[2, 3] + matrix[4, 5]
 72    stiffness_deviat = upper_tri_to_symmetric(stiffness_deviat)
 73    return stiffness_dilat, stiffness_deviat
 74
 75
 76@nb.njit(fastmath=True)
 77def mono_project(voigt_vector):
 78    """Project 21-component `voigt_vector` onto monoclinic symmetry subspace.
 79
 80    Monoclinic symmetry is characterised by 13 independent elasticity components.
 81
 82    See [Browaeys & Chevrot (2004)](https://doi.org/10.1111/j.1365-246X.2004.02415.x).
 83
 84    """
 85    out = voigt_vector.copy()
 86    inds = (9, 10, 12, 13, 15, 16, 18, 19)
 87    for i in inds:
 88        out[i] = 0
 89    return out
 90
 91
 92@nb.njit(fastmath=True)
 93def ortho_project(voigt_vector):
 94    """Project 21-component `voigt_vector` onto orthorhombic symmetry subspace.
 95
 96    Orthorhombic symmetry is characterised by 9 independent elasticity components.
 97
 98    See [Browaeys & Chevrot (2004)](https://doi.org/10.1111/j.1365-246X.2004.02415.x).
 99
100    """
101    out = voigt_vector.copy()
102    out[9:] = 0
103    return out
104
105
106@nb.njit(fastmath=True)
107def tetr_project(voigt_vector):
108    """Project 21-component `voigt_vector` onto tetragonal symmetry subspace.
109
110    Tetragonal symmetry is characterised by 6 independent elasticity components.
111
112    See [Browaeys & Chevrot (2004)](https://doi.org/10.1111/j.1365-246X.2004.02415.x).
113
114    """
115    out = ortho_project(voigt_vector)
116    for i, j in ((0, 1), (3, 4), (6, 7)):
117        for k in range(2):
118            out[i + k] = 0.5 * (voigt_vector[i] + voigt_vector[j])
119    return out
120
121
122@nb.njit(fastmath=True)
123def hex_project(voigt_vector):
124    """Project 21-component `voigt_vector` onto hexagonal symmetry subspace.
125
126    Hexagonal symmetry (a.k.a. transverse isotropy) is characterised by 5 independent
127    elasticity components.
128
129    See [Browaeys & Chevrot (2004)](https://doi.org/10.1111/j.1365-246X.2004.02415.x).
130
131    """
132    x = voigt_vector
133    out = np.zeros(21)
134    out[0] = out[1] = 3 / 8 * (x[0] + x[1]) + x[5] / 4 / np.sqrt(2) + x[8] / 4
135    out[2] = x[2]
136    out[3] = out[4] = (x[3] + x[4]) / 2
137    out[5] = (x[0] + x[1]) / 4 / np.sqrt(2) + 3 / 4 * x[5] - x[8] / 2 / np.sqrt(2)
138    out[6] = out[7] = (x[6] + x[7]) / 2
139    out[8] = (x[0] + x[1]) / 4 - x[5] / 2 / np.sqrt(2) + x[8] / 2
140    return out
141
142
143@nb.njit(fastmath=True)
144def upper_tri_to_symmetric(arr):
145    """Create symmetric array using upper triangle of input array.
146
147    Examples:
148
149    >>> import numpy as np
150    >>> upper_tri_to_symmetric(np.array([
151    ...         [ 1.,  2.,  3.,  4.],
152    ...         [ 0.,  5.,  6.,  7.],
153    ...         [ 0.,  0.,  8.,  9.],
154    ...         [ 9.,  0.,  0., 10.]
155    ... ]))
156    array([[ 1.,  2.,  3.,  4.],
157           [ 2.,  5.,  6.,  7.],
158           [ 3.,  6.,  8.,  9.],
159           [ 4.,  7.,  9., 10.]])
160
161    """
162    # <https://stackoverflow.com/questions/58718365/fast-way-to-convert-upper-triangular-matrix-into-symmetric-matrix>
163    upper_tri = np.triu(arr)
164    return np.where(upper_tri, upper_tri, upper_tri.transpose())
165
166
167@nb.njit(fastmath=True)
168def voigt_to_elastic_tensor(matrix):
169    """Create 4-th order elastic tensor from an equivalent Voigt matrix.
170
171    See also: `elastic_tensor_to_voigt`.
172
173    """
174    tensor = np.empty((3, 3, 3, 3))
175    for p in range(3):
176        for q in range(3):
177            delta_pq = 1 if p == q else 0
178            i = (p + 1) * delta_pq + (1 - delta_pq) * (7 - p - q) - 1
179            for r in range(3):
180                for s in range(3):
181                    delta_rs = 1 if r == s else 0
182                    j = (r + 1) * delta_rs + (1 - delta_rs) * (7 - r - s) - 1
183                    tensor[p, q, r, s] = matrix[i, j]
184    return tensor
185
186
187@nb.njit(fastmath=True)
188def elastic_tensor_to_voigt(tensor):
189    """Create a 6x6 Voigt matrix from an equivalent 4-th order elastic tensor."""
190    matrix = np.zeros((6, 6))
191    matrix_indices = np.zeros((6, 6))
192    for p in range(3):
193        for q in range(3):
194            delta_pq = 1 if p == q else 0
195            i = (p + 1) * delta_pq + (1 - delta_pq) * (7 - p - q) - 1
196            for r in range(3):
197                for s in range(3):
198                    delta_rs = 1 if r == s else 0
199                    j = (r + 1) * delta_rs + (1 - delta_rs) * (7 - r - s) - 1
200                    matrix[i, j] += tensor[p, q, r, s]
201                    matrix_indices[i, j] += 1
202    matrix /= matrix_indices
203    return (matrix + matrix.transpose()) / 2
204
205
206@nb.njit(fastmath=True)
207def voigt_matrix_to_vector(matrix):
208    """Create the 21-component Voigt vector equivalent to the 6x6 Voigt matrix."""
209    vector = np.zeros(21)
210    for i in range(3):
211        vector[i] = matrix[i, i]
212        vector[i + 3] = np.sqrt(2) * matrix[(i + 1) % 3, (i + 2) % 3]
213        vector[i + 6] = 2 * matrix[i + 3, i + 3]
214        vector[i + 9] = 2 * matrix[i, i + 3]
215        vector[i + 12] = 2 * matrix[(i + 2) % 3, i + 3]
216        vector[i + 15] = 2 * matrix[(i + 1) % 3, i + 3]
217        vector[i + 18] = 2 * np.sqrt(2) * matrix[(i + 1) % 3 + 3, (i + 2) % 3 + 3]
218    return vector
219
220
221@nb.njit(fastmath=True)
222def voigt_vector_to_matrix(vector):
223    """Create the 6x6 matrix representation of the 21-component Voigt vector.
224
225    See also: `voigt_matrix_to_vector`.
226
227    """
228    matrix = np.zeros((6, 6))
229    for i in range(3):
230        matrix[i, i] = vector[i]
231        matrix[i + 3, i + 3] = 0.5 * vector[i + 6]
232
233    matrix[1, 2] = 1 / np.sqrt(2) * vector[3]
234    matrix[0, 2] = 1 / np.sqrt(2) * vector[4]
235    matrix[0, 1] = 1 / np.sqrt(2) * vector[5]
236
237    matrix[0, 3] = 0.5 * vector[9]
238    matrix[1, 4] = 0.5 * vector[10]
239    matrix[2, 5] = 0.5 * vector[11]
240    matrix[2, 3] = 0.5 * vector[12]
241
242    matrix[0, 4] = 0.5 * vector[13]
243    matrix[1, 5] = 0.5 * vector[14]
244    matrix[1, 3] = 0.5 * vector[15]
245
246    matrix[2][4] = 0.5 * vector[16]
247    matrix[0][5] = 0.5 * vector[17]
248    matrix[4][5] = 0.5 * 1 / np.sqrt(2) * vector[18]
249    matrix[3][5] = 0.5 * 1 / np.sqrt(2) * vector[19]
250    matrix[3][4] = 0.5 * 1 / np.sqrt(2) * vector[20]
251    return upper_tri_to_symmetric(matrix)
252
253
254@nb.njit(fastmath=True)
255def rotate(tensor, rotation):
256    """Rotate 4-th order tensor using a 3x3 rotation matrix."""
257    rotated_tensor = np.zeros((3, 3, 3, 3))
258    for i in range(3):
259        for j in range(3):
260            for k in range(3):
261                for L in range(3):
262                    for a in range(3):
263                        for b in range(3):
264                            for c in range(3):
265                                for d in range(3):
266                                    rotated_tensor[i, j, k, L] += (
267                                        rotation[i, a]
268                                        * rotation[j, b]
269                                        * rotation[k, c]
270                                        * rotation[L, d]
271                                        * tensor[a, b, c, d]
272                                    )
273    return rotated_tensor
@nb.njit(fastmath=True)
def polar_decompose(matrix, left=True):
15@nb.njit(fastmath=True)
16def polar_decompose(matrix, left=True):
17    """Compute polar decomposition of M as either M = RU (right) or M = VP (left)."""
18    U, S, Vh = np.linalg.svd(matrix)
19    if left:
20        return U @ Vh, U @ (np.diag(S) @ U.transpose())
21    U_matrix = Vh.transpose() @ (np.diag(S) @ Vh)
22    return matrix @ np.linalg.inv(U_matrix), U_matrix

Compute polar decomposition of M as either M = RU (right) or M = VP (left).

@nb.njit(fastmath=True)
def invariants_second_order(tensor):
25@nb.njit(fastmath=True)
26def invariants_second_order(tensor):
27    """Calculate invariants of a second order tensor."""
28    return (
29        np.trace(tensor),
30        tensor[0, 0] * tensor[1, 1]
31        + tensor[1, 1] * tensor[2, 2]
32        + tensor[2, 2] * tensor[0, 0]
33        - tensor[0, 1] * tensor[1, 0]
34        - tensor[1, 2] * tensor[2, 1]
35        - tensor[2, 0] * tensor[0, 2],
36        np.linalg.det(tensor),
37    )

Calculate invariants of a second order tensor.

@nb.njit(fastmath=True)
def voigt_decompose(matrix: numpy.ndarray) -> tuple[numpy.ndarray, numpy.ndarray]:
40@nb.njit(fastmath=True)
41def voigt_decompose(matrix: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
42    """Decompose elastic tensor (as 6x6 Voigt matrix) into distinct contractions.
43
44    Return the only two independent contractions of the elastic tensor given as a 6x6
45    Voigt `matrix`. With reference to the full 4-th order elastic tensor, the
46    contractions are defined as:
47    - $d_{ij} = C_{ijkk}$ (dilatational stiffness tensor)
48    - $v_{ij} = C_{ijkj}$ (deviatoric stiffness tensor)
49
50    Any vector which is an eigenvector of both $d_{ij}$ and $v_{ij}$ is always normal to
51    a symmetry plane of the elastic medium.
52
53    See Equations 3.4 & 3.5 in [Browaeys & Chevrot (2004)](https://doi.org/10.1111/j.1365-246X.2004.02415.x).
54
55    """
56    # 1. Compose dᵢⱼ = Cᵢⱼₖₖ (dilatational stiffness tensor)
57    # Eq. 3.4 in Browaeys & Chevrot, 2004.
58    stiffness_dilat = np.empty((3, 3))
59    for i in range(3):
60        stiffness_dilat[i, i] = matrix[:3, i].sum()
61    stiffness_dilat[0, 1] = stiffness_dilat[1, 0] = matrix[:3, 5].sum()
62    stiffness_dilat[0, 2] = stiffness_dilat[2, 0] = matrix[:3, 4].sum()
63    stiffness_dilat[1, 2] = stiffness_dilat[2, 1] = matrix[:3, 3].sum()
64    # 2. Compose vᵢⱼ = Cᵢⱼₖⱼ (deviatoric stiffness tensor)
65    # Eq. 3.5, Browaeys & Chevrot, 2004.
66    stiffness_deviat = np.empty((3, 3))
67    stiffness_deviat[0, 0] = matrix[0, 0] + matrix[4, 4] + matrix[5, 5]
68    stiffness_deviat[1, 1] = matrix[1, 1] + matrix[3, 3] + matrix[5, 5]
69    stiffness_deviat[2, 2] = matrix[2, 2] + matrix[3, 3] + matrix[4, 4]
70    stiffness_deviat[0, 1] = matrix[0, 5] + matrix[1, 5] + matrix[3, 4]
71    stiffness_deviat[0, 2] = matrix[0, 4] + matrix[2, 4] + matrix[3, 5]
72    stiffness_deviat[1, 2] = matrix[1, 3] + matrix[2, 3] + matrix[4, 5]
73    stiffness_deviat = upper_tri_to_symmetric(stiffness_deviat)
74    return stiffness_dilat, stiffness_deviat

Decompose elastic tensor (as 6x6 Voigt matrix) into distinct contractions.

Return the only two independent contractions of the elastic tensor given as a 6x6 Voigt matrix. With reference to the full 4-th order elastic tensor, the contractions are defined as:

  • $d_{ij} = C_{ijkk}$ (dilatational stiffness tensor)
  • $v_{ij} = C_{ijkj}$ (deviatoric stiffness tensor)

Any vector which is an eigenvector of both $d_{ij}$ and $v_{ij}$ is always normal to a symmetry plane of the elastic medium.

See Equations 3.4 & 3.5 in Browaeys & Chevrot (2004).

@nb.njit(fastmath=True)
def mono_project(voigt_vector):
77@nb.njit(fastmath=True)
78def mono_project(voigt_vector):
79    """Project 21-component `voigt_vector` onto monoclinic symmetry subspace.
80
81    Monoclinic symmetry is characterised by 13 independent elasticity components.
82
83    See [Browaeys & Chevrot (2004)](https://doi.org/10.1111/j.1365-246X.2004.02415.x).
84
85    """
86    out = voigt_vector.copy()
87    inds = (9, 10, 12, 13, 15, 16, 18, 19)
88    for i in inds:
89        out[i] = 0
90    return out

Project 21-component voigt_vector onto monoclinic symmetry subspace.

Monoclinic symmetry is characterised by 13 independent elasticity components.

See Browaeys & Chevrot (2004).

@nb.njit(fastmath=True)
def ortho_project(voigt_vector):
 93@nb.njit(fastmath=True)
 94def ortho_project(voigt_vector):
 95    """Project 21-component `voigt_vector` onto orthorhombic symmetry subspace.
 96
 97    Orthorhombic symmetry is characterised by 9 independent elasticity components.
 98
 99    See [Browaeys & Chevrot (2004)](https://doi.org/10.1111/j.1365-246X.2004.02415.x).
100
101    """
102    out = voigt_vector.copy()
103    out[9:] = 0
104    return out

Project 21-component voigt_vector onto orthorhombic symmetry subspace.

Orthorhombic symmetry is characterised by 9 independent elasticity components.

See Browaeys & Chevrot (2004).

@nb.njit(fastmath=True)
def tetr_project(voigt_vector):
107@nb.njit(fastmath=True)
108def tetr_project(voigt_vector):
109    """Project 21-component `voigt_vector` onto tetragonal symmetry subspace.
110
111    Tetragonal symmetry is characterised by 6 independent elasticity components.
112
113    See [Browaeys & Chevrot (2004)](https://doi.org/10.1111/j.1365-246X.2004.02415.x).
114
115    """
116    out = ortho_project(voigt_vector)
117    for i, j in ((0, 1), (3, 4), (6, 7)):
118        for k in range(2):
119            out[i + k] = 0.5 * (voigt_vector[i] + voigt_vector[j])
120    return out

Project 21-component voigt_vector onto tetragonal symmetry subspace.

Tetragonal symmetry is characterised by 6 independent elasticity components.

See Browaeys & Chevrot (2004).

@nb.njit(fastmath=True)
def hex_project(voigt_vector):
123@nb.njit(fastmath=True)
124def hex_project(voigt_vector):
125    """Project 21-component `voigt_vector` onto hexagonal symmetry subspace.
126
127    Hexagonal symmetry (a.k.a. transverse isotropy) is characterised by 5 independent
128    elasticity components.
129
130    See [Browaeys & Chevrot (2004)](https://doi.org/10.1111/j.1365-246X.2004.02415.x).
131
132    """
133    x = voigt_vector
134    out = np.zeros(21)
135    out[0] = out[1] = 3 / 8 * (x[0] + x[1]) + x[5] / 4 / np.sqrt(2) + x[8] / 4
136    out[2] = x[2]
137    out[3] = out[4] = (x[3] + x[4]) / 2
138    out[5] = (x[0] + x[1]) / 4 / np.sqrt(2) + 3 / 4 * x[5] - x[8] / 2 / np.sqrt(2)
139    out[6] = out[7] = (x[6] + x[7]) / 2
140    out[8] = (x[0] + x[1]) / 4 - x[5] / 2 / np.sqrt(2) + x[8] / 2
141    return out

Project 21-component voigt_vector onto hexagonal symmetry subspace.

Hexagonal symmetry (a.k.a. transverse isotropy) is characterised by 5 independent elasticity components.

See Browaeys & Chevrot (2004).

@nb.njit(fastmath=True)
def upper_tri_to_symmetric(arr):
144@nb.njit(fastmath=True)
145def upper_tri_to_symmetric(arr):
146    """Create symmetric array using upper triangle of input array.
147
148    Examples:
149
150    >>> import numpy as np
151    >>> upper_tri_to_symmetric(np.array([
152    ...         [ 1.,  2.,  3.,  4.],
153    ...         [ 0.,  5.,  6.,  7.],
154    ...         [ 0.,  0.,  8.,  9.],
155    ...         [ 9.,  0.,  0., 10.]
156    ... ]))
157    array([[ 1.,  2.,  3.,  4.],
158           [ 2.,  5.,  6.,  7.],
159           [ 3.,  6.,  8.,  9.],
160           [ 4.,  7.,  9., 10.]])
161
162    """
163    # <https://stackoverflow.com/questions/58718365/fast-way-to-convert-upper-triangular-matrix-into-symmetric-matrix>
164    upper_tri = np.triu(arr)
165    return np.where(upper_tri, upper_tri, upper_tri.transpose())

Create symmetric array using upper triangle of input array.

Examples:

>>> import numpy as np
>>> upper_tri_to_symmetric(np.array([
...         [ 1.,  2.,  3.,  4.],
...         [ 0.,  5.,  6.,  7.],
...         [ 0.,  0.,  8.,  9.],
...         [ 9.,  0.,  0., 10.]
... ]))
array([[ 1.,  2.,  3.,  4.],
       [ 2.,  5.,  6.,  7.],
       [ 3.,  6.,  8.,  9.],
       [ 4.,  7.,  9., 10.]])
@nb.njit(fastmath=True)
def voigt_to_elastic_tensor(matrix):
168@nb.njit(fastmath=True)
169def voigt_to_elastic_tensor(matrix):
170    """Create 4-th order elastic tensor from an equivalent Voigt matrix.
171
172    See also: `elastic_tensor_to_voigt`.
173
174    """
175    tensor = np.empty((3, 3, 3, 3))
176    for p in range(3):
177        for q in range(3):
178            delta_pq = 1 if p == q else 0
179            i = (p + 1) * delta_pq + (1 - delta_pq) * (7 - p - q) - 1
180            for r in range(3):
181                for s in range(3):
182                    delta_rs = 1 if r == s else 0
183                    j = (r + 1) * delta_rs + (1 - delta_rs) * (7 - r - s) - 1
184                    tensor[p, q, r, s] = matrix[i, j]
185    return tensor

Create 4-th order elastic tensor from an equivalent Voigt matrix.

See also: elastic_tensor_to_voigt.

@nb.njit(fastmath=True)
def elastic_tensor_to_voigt(tensor):
188@nb.njit(fastmath=True)
189def elastic_tensor_to_voigt(tensor):
190    """Create a 6x6 Voigt matrix from an equivalent 4-th order elastic tensor."""
191    matrix = np.zeros((6, 6))
192    matrix_indices = np.zeros((6, 6))
193    for p in range(3):
194        for q in range(3):
195            delta_pq = 1 if p == q else 0
196            i = (p + 1) * delta_pq + (1 - delta_pq) * (7 - p - q) - 1
197            for r in range(3):
198                for s in range(3):
199                    delta_rs = 1 if r == s else 0
200                    j = (r + 1) * delta_rs + (1 - delta_rs) * (7 - r - s) - 1
201                    matrix[i, j] += tensor[p, q, r, s]
202                    matrix_indices[i, j] += 1
203    matrix /= matrix_indices
204    return (matrix + matrix.transpose()) / 2

Create a 6x6 Voigt matrix from an equivalent 4-th order elastic tensor.

@nb.njit(fastmath=True)
def voigt_matrix_to_vector(matrix):
207@nb.njit(fastmath=True)
208def voigt_matrix_to_vector(matrix):
209    """Create the 21-component Voigt vector equivalent to the 6x6 Voigt matrix."""
210    vector = np.zeros(21)
211    for i in range(3):
212        vector[i] = matrix[i, i]
213        vector[i + 3] = np.sqrt(2) * matrix[(i + 1) % 3, (i + 2) % 3]
214        vector[i + 6] = 2 * matrix[i + 3, i + 3]
215        vector[i + 9] = 2 * matrix[i, i + 3]
216        vector[i + 12] = 2 * matrix[(i + 2) % 3, i + 3]
217        vector[i + 15] = 2 * matrix[(i + 1) % 3, i + 3]
218        vector[i + 18] = 2 * np.sqrt(2) * matrix[(i + 1) % 3 + 3, (i + 2) % 3 + 3]
219    return vector

Create the 21-component Voigt vector equivalent to the 6x6 Voigt matrix.

@nb.njit(fastmath=True)
def voigt_vector_to_matrix(vector):
222@nb.njit(fastmath=True)
223def voigt_vector_to_matrix(vector):
224    """Create the 6x6 matrix representation of the 21-component Voigt vector.
225
226    See also: `voigt_matrix_to_vector`.
227
228    """
229    matrix = np.zeros((6, 6))
230    for i in range(3):
231        matrix[i, i] = vector[i]
232        matrix[i + 3, i + 3] = 0.5 * vector[i + 6]
233
234    matrix[1, 2] = 1 / np.sqrt(2) * vector[3]
235    matrix[0, 2] = 1 / np.sqrt(2) * vector[4]
236    matrix[0, 1] = 1 / np.sqrt(2) * vector[5]
237
238    matrix[0, 3] = 0.5 * vector[9]
239    matrix[1, 4] = 0.5 * vector[10]
240    matrix[2, 5] = 0.5 * vector[11]
241    matrix[2, 3] = 0.5 * vector[12]
242
243    matrix[0, 4] = 0.5 * vector[13]
244    matrix[1, 5] = 0.5 * vector[14]
245    matrix[1, 3] = 0.5 * vector[15]
246
247    matrix[2][4] = 0.5 * vector[16]
248    matrix[0][5] = 0.5 * vector[17]
249    matrix[4][5] = 0.5 * 1 / np.sqrt(2) * vector[18]
250    matrix[3][5] = 0.5 * 1 / np.sqrt(2) * vector[19]
251    matrix[3][4] = 0.5 * 1 / np.sqrt(2) * vector[20]
252    return upper_tri_to_symmetric(matrix)

Create the 6x6 matrix representation of the 21-component Voigt vector.

See also: voigt_matrix_to_vector.

@nb.njit(fastmath=True)
def rotate(tensor, rotation):
255@nb.njit(fastmath=True)
256def rotate(tensor, rotation):
257    """Rotate 4-th order tensor using a 3x3 rotation matrix."""
258    rotated_tensor = np.zeros((3, 3, 3, 3))
259    for i in range(3):
260        for j in range(3):
261            for k in range(3):
262                for L in range(3):
263                    for a in range(3):
264                        for b in range(3):
265                            for c in range(3):
266                                for d in range(3):
267                                    rotated_tensor[i, j, k, L] += (
268                                        rotation[i, a]
269                                        * rotation[j, b]
270                                        * rotation[k, c]
271                                        * rotation[L, d]
272                                        * tensor[a, b, c, d]
273                                    )
274    return rotated_tensor

Rotate 4-th order tensor using a 3x3 rotation matrix.