Fourth-order tensors#

In this notebook, we will show various representations of fourth-order tensors and conversion between them.

Various representation of fourth-order tensors are implemented in the tensorconvert.FourthOrderTensor class.

import sympy
from tensorconvert import SecondOrderTensor, FourthOrderTensor

sympy.init_printing(use_latex="mathjax")

Representations#

By default, FourthOrderTensor is initialized with a generic fourth-order array ArraySymbol.

The spatial dimension is indicated by the dim parameter. By default, we have dim = 3.

The symmetry of the array object can be prescribed through the symmetry parameter

  • No symmetry symmetry=None

  • Minor symmetry symmetry="minor", which implies a[i, j, k, l] = a[j, i, k, l] = a[i, j, l, k]. In practice, the expressions containing a[j, i, k, l] and a[i, j, l, k] with j > i and l > k will be replaced by a[i, j, l, k].

  • (Default) Major symmetry symmetry="major", which additionally assumes that a[i, j, k, l] = a[k, l, i, j]. In practice, this implies that the matrix representation will be symmetric using the upper-diagonal part.

Fourth-order tensors are regarded as a linear operator \(T\) in the vector space \(V\) of second-order tensors

\[ T:V\to V. \]

The dimension of the vector space \(V\) is not to be confused with the spatial dimension. For 3-d symmetric second-order tensors, the dimension of \(V\) is 6, since 6 basis vectors are required to represent them using for example Voigt or Mandel notation.

After having chosen a basis for the input and the output spaces \(V\) (which can be the same), the fourth-order tensor \(\mathbb{C}\) can be represented by a matrix

\[\begin{split} T(\mathbf{v}_j)=\sum_{i=1}^n T_{ij}\mathbf{w}_i\quad\implies T=\begin{bmatrix} T_{11} & T_{12} & \ldots & T_{1n} \\ T_{21} & T_{22} & \ldots & T_{2n} \\ \ldots & \ldots & \ldots & \ldots \\ T_{n1} & T_{n2} & \ldots & T_{nn} \\ \end{bmatrix}. \end{split}\]

Here, \((\mathbf{v}_1,\mathbf{v}_2,\ldots,\mathbf{v}_n)\) is the basis chosen to represent input second-order tensors, while \((\mathbf{w}_1, \mathbf{w}_2, \ldots, \mathbf{w}_n)\) is used to represent output second-order tensors.

Voigt notation#

Voigt notation assumes that the input is a strain-like tensor while the output is stress-like. Hence

  • Input basis \(\mathbf{v}\) is SecondOrderTensor().basis_voigt_strain().

  • Output basis \(\mathbf{w}\) is SecondOrderTensor().basis_voigt_stress().

FourthOrderTensor().as_voigt()
\[\begin{split}\displaystyle \left[\begin{matrix}{{a}_{0, 0, 0, 0}} & {{a}_{0, 0, 1, 1}} & {{a}_{0, 0, 2, 2}} & {{a}_{0, 0, 0, 1}} & {{a}_{0, 0, 0, 2}} & {{a}_{0, 0, 1, 2}}\\{{a}_{0, 0, 1, 1}} & {{a}_{1, 1, 1, 1}} & {{a}_{1, 1, 2, 2}} & {{a}_{1, 1, 0, 1}} & {{a}_{1, 1, 0, 2}} & {{a}_{1, 1, 1, 2}}\\{{a}_{0, 0, 2, 2}} & {{a}_{1, 1, 2, 2}} & {{a}_{2, 2, 2, 2}} & {{a}_{2, 2, 0, 1}} & {{a}_{2, 2, 0, 2}} & {{a}_{2, 2, 1, 2}}\\{{a}_{0, 0, 0, 1}} & {{a}_{1, 1, 0, 1}} & {{a}_{2, 2, 0, 1}} & {{a}_{0, 1, 0, 1}} & {{a}_{0, 1, 0, 2}} & {{a}_{0, 1, 1, 2}}\\{{a}_{0, 0, 0, 2}} & {{a}_{1, 1, 0, 2}} & {{a}_{2, 2, 0, 2}} & {{a}_{0, 1, 0, 2}} & {{a}_{0, 2, 0, 2}} & {{a}_{0, 2, 1, 2}}\\{{a}_{0, 0, 1, 2}} & {{a}_{1, 1, 1, 2}} & {{a}_{2, 2, 1, 2}} & {{a}_{0, 1, 1, 2}} & {{a}_{0, 2, 1, 2}} & {{a}_{1, 2, 1, 2}}\end{matrix}\right]\end{split}\]

The ordering of the shear (off-diagonal) components can also be specified by the ordering parameter. By default, we assume FourthOrderTensor(ordering="121323") as for second-order tensors.

FourthOrderTensor(ordering="122313").as_voigt()
\[\begin{split}\displaystyle \left[\begin{matrix}{{a}_{0, 0, 0, 0}} & {{a}_{0, 0, 1, 1}} & {{a}_{0, 0, 2, 2}} & {{a}_{0, 0, 0, 1}} & {{a}_{0, 0, 1, 2}} & {{a}_{0, 0, 0, 2}}\\{{a}_{0, 0, 1, 1}} & {{a}_{1, 1, 1, 1}} & {{a}_{1, 1, 2, 2}} & {{a}_{1, 1, 0, 1}} & {{a}_{1, 1, 1, 2}} & {{a}_{1, 1, 0, 2}}\\{{a}_{0, 0, 2, 2}} & {{a}_{1, 1, 2, 2}} & {{a}_{2, 2, 2, 2}} & {{a}_{2, 2, 0, 1}} & {{a}_{2, 2, 1, 2}} & {{a}_{2, 2, 0, 2}}\\{{a}_{0, 0, 0, 1}} & {{a}_{1, 1, 0, 1}} & {{a}_{2, 2, 0, 1}} & {{a}_{0, 1, 0, 1}} & {{a}_{0, 1, 1, 2}} & {{a}_{0, 1, 0, 2}}\\{{a}_{0, 0, 1, 2}} & {{a}_{1, 1, 1, 2}} & {{a}_{2, 2, 1, 2}} & {{a}_{0, 1, 1, 2}} & {{a}_{1, 2, 1, 2}} & {{a}_{1, 2, 0, 2}}\\{{a}_{0, 0, 0, 2}} & {{a}_{1, 1, 0, 2}} & {{a}_{2, 2, 0, 2}} & {{a}_{0, 1, 0, 2}} & {{a}_{1, 2, 0, 2}} & {{a}_{0, 2, 0, 2}}\end{matrix}\right]\end{split}\]

It can be observed that by default, the matrix representation is symmetric, due to major symmetry. With symmetry = "minor", the output matrix is no longer symmetric.

FourthOrderTensor(symmetry="minor").as_voigt()
\[\begin{split}\displaystyle \left[\begin{matrix}{{a}_{0, 0, 0, 0}} & {{a}_{0, 0, 1, 1}} & {{a}_{0, 0, 2, 2}} & {{a}_{0, 0, 0, 1}} & {{a}_{0, 0, 0, 2}} & {{a}_{0, 0, 1, 2}}\\{{a}_{1, 1, 0, 0}} & {{a}_{1, 1, 1, 1}} & {{a}_{1, 1, 2, 2}} & {{a}_{1, 1, 0, 1}} & {{a}_{1, 1, 0, 2}} & {{a}_{1, 1, 1, 2}}\\{{a}_{2, 2, 0, 0}} & {{a}_{2, 2, 1, 1}} & {{a}_{2, 2, 2, 2}} & {{a}_{2, 2, 0, 1}} & {{a}_{2, 2, 0, 2}} & {{a}_{2, 2, 1, 2}}\\{{a}_{0, 1, 0, 0}} & {{a}_{0, 1, 1, 1}} & {{a}_{0, 1, 2, 2}} & {{a}_{0, 1, 0, 1}} & {{a}_{0, 1, 0, 2}} & {{a}_{0, 1, 1, 2}}\\{{a}_{0, 2, 0, 0}} & {{a}_{0, 2, 1, 1}} & {{a}_{0, 2, 2, 2}} & {{a}_{0, 2, 0, 1}} & {{a}_{0, 2, 0, 2}} & {{a}_{0, 2, 1, 2}}\\{{a}_{1, 2, 0, 0}} & {{a}_{1, 2, 1, 1}} & {{a}_{1, 2, 2, 2}} & {{a}_{1, 2, 0, 1}} & {{a}_{1, 2, 0, 2}} & {{a}_{1, 2, 1, 2}}\end{matrix}\right]\end{split}\]

Mandel notation#

Mandel notation uses the same basis vectors to represent the input and the output second-order tensors. Hence

  • Input basis \(\mathbf{v}\) is SecondOrderTensor().basis_mandel().

  • Output basis \(\mathbf{w}\) is SecondOrderTensor().basis_mandel().

FourthOrderTensor().as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}{{a}_{0, 0, 0, 0}} & {{a}_{0, 0, 1, 1}} & {{a}_{0, 0, 2, 2}} & \sqrt{2} {{a}_{0, 0, 0, 1}} & \sqrt{2} {{a}_{0, 0, 0, 2}} & \sqrt{2} {{a}_{0, 0, 1, 2}}\\{{a}_{0, 0, 1, 1}} & {{a}_{1, 1, 1, 1}} & {{a}_{1, 1, 2, 2}} & \sqrt{2} {{a}_{1, 1, 0, 1}} & \sqrt{2} {{a}_{1, 1, 0, 2}} & \sqrt{2} {{a}_{1, 1, 1, 2}}\\{{a}_{0, 0, 2, 2}} & {{a}_{1, 1, 2, 2}} & {{a}_{2, 2, 2, 2}} & \sqrt{2} {{a}_{2, 2, 0, 1}} & \sqrt{2} {{a}_{2, 2, 0, 2}} & \sqrt{2} {{a}_{2, 2, 1, 2}}\\\sqrt{2} {{a}_{0, 0, 0, 1}} & \sqrt{2} {{a}_{1, 1, 0, 1}} & \sqrt{2} {{a}_{2, 2, 0, 1}} & 2 {{a}_{0, 1, 0, 1}} & 2 {{a}_{0, 1, 0, 2}} & 2 {{a}_{0, 1, 1, 2}}\\\sqrt{2} {{a}_{0, 0, 0, 2}} & \sqrt{2} {{a}_{1, 1, 0, 2}} & \sqrt{2} {{a}_{2, 2, 0, 2}} & 2 {{a}_{0, 1, 0, 2}} & 2 {{a}_{0, 2, 0, 2}} & 2 {{a}_{0, 2, 1, 2}}\\\sqrt{2} {{a}_{0, 0, 1, 2}} & \sqrt{2} {{a}_{1, 1, 1, 2}} & \sqrt{2} {{a}_{2, 2, 1, 2}} & 2 {{a}_{0, 1, 1, 2}} & 2 {{a}_{0, 2, 1, 2}} & 2 {{a}_{1, 2, 1, 2}}\end{matrix}\right]\end{split}\]

The spatial dimension can be specified by the dim parameter.

FourthOrderTensor(dim=2, symmetry="minor").as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}{{a}_{0, 0, 0, 0}} & {{a}_{0, 0, 1, 1}} & \sqrt{2} {{a}_{0, 0, 0, 1}}\\{{a}_{1, 1, 0, 0}} & {{a}_{1, 1, 1, 1}} & \sqrt{2} {{a}_{1, 1, 0, 1}}\\\sqrt{2} {{a}_{0, 1, 0, 0}} & \sqrt{2} {{a}_{0, 1, 1, 1}} & 2 {{a}_{0, 1, 0, 1}}\end{matrix}\right]\end{split}\]

Unsymmetric notation#

For fourth-order tensors without any symmetry (which means the input and the output tensors are not symmetric), the unsymmetric notation can be used

  • Input basis \(\mathbf{v}\) is SecondOrderTensor().basis_unsym().

  • Output basis \(\mathbf{w}\) is SecondOrderTensor().basis_unsym().

FourthOrderTensor(symmetry=None).as_unsym()
\[\begin{split}\displaystyle \left[\begin{matrix}{{a}_{0, 0, 0, 0}} & {{a}_{0, 0, 1, 1}} & {{a}_{0, 0, 2, 2}} & {{a}_{0, 0, 0, 1}} & {{a}_{0, 0, 1, 0}} & {{a}_{0, 0, 0, 2}} & {{a}_{0, 0, 2, 0}} & {{a}_{0, 0, 1, 2}} & {{a}_{0, 0, 2, 1}}\\{{a}_{1, 1, 0, 0}} & {{a}_{1, 1, 1, 1}} & {{a}_{1, 1, 2, 2}} & {{a}_{1, 1, 0, 1}} & {{a}_{1, 1, 1, 0}} & {{a}_{1, 1, 0, 2}} & {{a}_{1, 1, 2, 0}} & {{a}_{1, 1, 1, 2}} & {{a}_{1, 1, 2, 1}}\\{{a}_{2, 2, 0, 0}} & {{a}_{2, 2, 1, 1}} & {{a}_{2, 2, 2, 2}} & {{a}_{2, 2, 0, 1}} & {{a}_{2, 2, 1, 0}} & {{a}_{2, 2, 0, 2}} & {{a}_{2, 2, 2, 0}} & {{a}_{2, 2, 1, 2}} & {{a}_{2, 2, 2, 1}}\\{{a}_{0, 1, 0, 0}} & {{a}_{0, 1, 1, 1}} & {{a}_{0, 1, 2, 2}} & {{a}_{0, 1, 0, 1}} & {{a}_{0, 1, 1, 0}} & {{a}_{0, 1, 0, 2}} & {{a}_{0, 1, 2, 0}} & {{a}_{0, 1, 1, 2}} & {{a}_{0, 1, 2, 1}}\\{{a}_{1, 0, 0, 0}} & {{a}_{1, 0, 1, 1}} & {{a}_{1, 0, 2, 2}} & {{a}_{1, 0, 0, 1}} & {{a}_{1, 0, 1, 0}} & {{a}_{1, 0, 0, 2}} & {{a}_{1, 0, 2, 0}} & {{a}_{1, 0, 1, 2}} & {{a}_{1, 0, 2, 1}}\\{{a}_{0, 2, 0, 0}} & {{a}_{0, 2, 1, 1}} & {{a}_{0, 2, 2, 2}} & {{a}_{0, 2, 0, 1}} & {{a}_{0, 2, 1, 0}} & {{a}_{0, 2, 0, 2}} & {{a}_{0, 2, 2, 0}} & {{a}_{0, 2, 1, 2}} & {{a}_{0, 2, 2, 1}}\\{{a}_{2, 0, 0, 0}} & {{a}_{2, 0, 1, 1}} & {{a}_{2, 0, 2, 2}} & {{a}_{2, 0, 0, 1}} & {{a}_{2, 0, 1, 0}} & {{a}_{2, 0, 0, 2}} & {{a}_{2, 0, 2, 0}} & {{a}_{2, 0, 1, 2}} & {{a}_{2, 0, 2, 1}}\\{{a}_{1, 2, 0, 0}} & {{a}_{1, 2, 1, 1}} & {{a}_{1, 2, 2, 2}} & {{a}_{1, 2, 0, 1}} & {{a}_{1, 2, 1, 0}} & {{a}_{1, 2, 0, 2}} & {{a}_{1, 2, 2, 0}} & {{a}_{1, 2, 1, 2}} & {{a}_{1, 2, 2, 1}}\\{{a}_{2, 1, 0, 0}} & {{a}_{2, 1, 1, 1}} & {{a}_{2, 1, 2, 2}} & {{a}_{2, 1, 0, 1}} & {{a}_{2, 1, 1, 0}} & {{a}_{2, 1, 0, 2}} & {{a}_{2, 1, 2, 0}} & {{a}_{2, 1, 1, 2}} & {{a}_{2, 1, 2, 1}}\end{matrix}\right]\end{split}\]
FourthOrderTensor(dim=2, symmetry=None).as_unsym()
\[\begin{split}\displaystyle \left[\begin{matrix}{{a}_{0, 0, 0, 0}} & {{a}_{0, 0, 1, 1}} & {{a}_{0, 0, 0, 1}} & {{a}_{0, 0, 1, 0}}\\{{a}_{1, 1, 0, 0}} & {{a}_{1, 1, 1, 1}} & {{a}_{1, 1, 0, 1}} & {{a}_{1, 1, 1, 0}}\\{{a}_{0, 1, 0, 0}} & {{a}_{0, 1, 1, 1}} & {{a}_{0, 1, 0, 1}} & {{a}_{0, 1, 1, 0}}\\{{a}_{1, 0, 0, 0}} & {{a}_{1, 0, 1, 1}} & {{a}_{1, 0, 0, 1}} & {{a}_{1, 0, 1, 0}}\end{matrix}\right]\end{split}\]

Array#

The underlying fourth-order array can also be shown via the as_array method.

FourthOrderTensor(dim=2).as_array()
\[\displaystyle a\]

By default, it is a ArraySymbol object. Its components can be shown via the as_explicit method.

FourthOrderTensor(dim=2).as_array().as_explicit()
\[\begin{split}\displaystyle \left[\begin{matrix}\left[\begin{matrix}{{a}_{0, 0, 0, 0}} & {{a}_{0, 0, 0, 1}}\\{{a}_{0, 0, 1, 0}} & {{a}_{0, 0, 1, 1}}\end{matrix}\right] & \left[\begin{matrix}{{a}_{0, 1, 0, 0}} & {{a}_{0, 1, 0, 1}}\\{{a}_{0, 1, 1, 0}} & {{a}_{0, 1, 1, 1}}\end{matrix}\right]\\\left[\begin{matrix}{{a}_{1, 0, 0, 0}} & {{a}_{1, 0, 0, 1}}\\{{a}_{1, 0, 1, 0}} & {{a}_{1, 0, 1, 1}}\end{matrix}\right] & \left[\begin{matrix}{{a}_{1, 1, 0, 0}} & {{a}_{1, 1, 0, 1}}\\{{a}_{1, 1, 1, 0}} & {{a}_{1, 1, 1, 1}}\end{matrix}\right]\end{matrix}\right]\end{split}\]

Linear operator#

FourthOrderTensor can also be represented as a linear operator \(T:V\to V\) in the space of second-order tensors. A Python function operating on sympy.Matrix is returned.

dim = 2
x = sympy.randMatrix(dim, dim)
x += x.T
FourthOrderTensor(dim=dim).as_operator()(x)
\[\begin{split}\displaystyle \left[\begin{matrix}84 {{a}_{0, 0, 0, 0}} + 254 {{a}_{0, 0, 0, 1}} + 142 {{a}_{0, 0, 1, 1}} & 84 {{a}_{0, 0, 0, 1}} + 254 {{a}_{0, 1, 0, 1}} + 142 {{a}_{1, 1, 0, 1}}\\84 {{a}_{0, 0, 0, 1}} + 254 {{a}_{0, 1, 0, 1}} + 142 {{a}_{1, 1, 0, 1}} & 84 {{a}_{0, 0, 1, 1}} + 254 {{a}_{1, 1, 0, 1}} + 142 {{a}_{1, 1, 1, 1}}\end{matrix}\right]\end{split}\]

To directly apply the current fourth-order tensor to a second-order tensor, apply can be used. It is equal to self.as_operator()(x).

FourthOrderTensor(dim=dim).apply(x)
\[\begin{split}\displaystyle \left[\begin{matrix}84 {{a}_{0, 0, 0, 0}} + 254 {{a}_{0, 0, 0, 1}} + 142 {{a}_{0, 0, 1, 1}} & 84 {{a}_{0, 0, 0, 1}} + 254 {{a}_{0, 1, 0, 1}} + 142 {{a}_{1, 1, 0, 1}}\\84 {{a}_{0, 0, 0, 1}} + 254 {{a}_{0, 1, 0, 1}} + 142 {{a}_{1, 1, 0, 1}} & 84 {{a}_{0, 0, 1, 1}} + 254 {{a}_{1, 1, 0, 1}} + 142 {{a}_{1, 1, 1, 1}}\end{matrix}\right]\end{split}\]

Initialization by matrix representations#

The default constructor of FourthOrderTensor uses a generic tensor object. It can also be initialized from the previous matrix representations.

dim = 3
n = 6 if dim == 3 else 3  # symmetric second-order tensors
a_mandel = sympy.randMatrix(n, n)
a_mandel += a_mandel.T
a_mandel  # Mandel representation of the tensor
\[\begin{split}\displaystyle \left[\begin{matrix}174 & 102 & 77 & 138 & 181 & 72\\102 & 80 & 136 & 141 & 43 & 145\\77 & 136 & 56 & 110 & 97 & 135\\138 & 141 & 110 & 184 & 132 & 144\\181 & 43 & 97 & 132 & 94 & 170\\72 & 145 & 135 & 144 & 170 & 100\end{matrix}\right]\end{split}\]
a = FourthOrderTensor(dim).from_mandel(a_mandel)  # initialize from Mandel notation
a.as_voigt()  # now, represent as Voigt notation
\[\begin{split}\displaystyle \left[\begin{matrix}174 & 102 & 77 & 69 \sqrt{2} & \frac{181 \sqrt{2}}{2} & 36 \sqrt{2}\\102 & 80 & 136 & \frac{141 \sqrt{2}}{2} & \frac{43 \sqrt{2}}{2} & \frac{145 \sqrt{2}}{2}\\77 & 136 & 56 & 55 \sqrt{2} & \frac{97 \sqrt{2}}{2} & \frac{135 \sqrt{2}}{2}\\69 \sqrt{2} & \frac{141 \sqrt{2}}{2} & 55 \sqrt{2} & 92 & 66 & 72\\\frac{181 \sqrt{2}}{2} & \frac{43 \sqrt{2}}{2} & \frac{97 \sqrt{2}}{2} & 66 & 47 & 85\\36 \sqrt{2} & \frac{145 \sqrt{2}}{2} & \frac{135 \sqrt{2}}{2} & 72 & 85 & 50\end{matrix}\right]\end{split}\]

Now, we initialize from this Voigt notation and then represent as Mandel notation. We should recover the initial matrix.

Thanks to the Fluent API, as_mandel can be applied directly after from_voigt.

FourthOrderTensor(dim).from_voigt(a.as_voigt()).as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}174 & 102 & 77 & 138 & 181 & 72\\102 & 80 & 136 & 141 & 43 & 145\\77 & 136 & 56 & 110 & 97 & 135\\138 & 141 & 110 & 184 & 132 & 144\\181 & 43 & 97 & 132 & 94 & 170\\72 & 145 & 135 & 144 & 170 & 100\end{matrix}\right]\end{split}\]
FourthOrderTensor(dim).from_voigt(a.as_voigt()).as_mandel() == a_mandel
True

Initialization by linear operator#

FourthOrderTensor can also be initialized from a linear operator \(T:V\to V\) defining the fourth-order tensor. A Python function operating on sympy.Matrix is required.

Identity#

The identity map just return the input tensor as the output.

def identity(x):
    return x

Using Voigt notation, the matrix representation of the identity map is not identity!

FourthOrderTensor().from_operator(identity).as_voigt()
\[\begin{split}\displaystyle \left[\begin{matrix}1 & 0 & 0 & 0 & 0 & 0\\0 & 1 & 0 & 0 & 0 & 0\\0 & 0 & 1 & 0 & 0 & 0\\0 & 0 & 0 & \frac{1}{2} & 0 & 0\\0 & 0 & 0 & 0 & \frac{1}{2} & 0\\0 & 0 & 0 & 0 & 0 & \frac{1}{2}\end{matrix}\right]\end{split}\]

By comparison, with Mandel notation the matrix identity is well obtained.

FourthOrderTensor(dim=2).from_operator(identity).as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}1 & 0 & 0\\0 & 1 & 0\\0 & 0 & 1\end{matrix}\right]\end{split}\]

Deviatoric operator#

The deviatoric operator computes the trace-free part of a second-order tensor. The trace of the output is zero.

def dev(eps):
    dim = eps.shape[0]
    tr = sympy.trace(eps)
    return eps - tr / dim * sympy.eye(dim)
FourthOrderTensor().from_operator(dev).as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}\frac{2}{3} & - \frac{1}{3} & - \frac{1}{3} & 0 & 0 & 0\\- \frac{1}{3} & \frac{2}{3} & - \frac{1}{3} & 0 & 0 & 0\\- \frac{1}{3} & - \frac{1}{3} & \frac{2}{3} & 0 & 0 & 0\\0 & 0 & 0 & 1 & 0 & 0\\0 & 0 & 0 & 0 & 1 & 0\\0 & 0 & 0 & 0 & 0 & 1\end{matrix}\right]\end{split}\]
FourthOrderTensor(dim=2).from_operator(dev).as_voigt()
\[\begin{split}\displaystyle \left[\begin{matrix}\frac{1}{2} & - \frac{1}{2} & 0\\- \frac{1}{2} & \frac{1}{2} & 0\\0 & 0 & \frac{1}{2}\end{matrix}\right]\end{split}\]

Isotropic linear elasticity#

The fourth-order isotropic linear elastic stiffness tensor can be defined using two Lamé constants \(\lambda\) and \(\mu\)

\[ \boldsymbol{\sigma}=\mathbb{C}\boldsymbol{\varepsilon}=\lambda\cdot\mathrm{tr}(\boldsymbol{\varepsilon})\cdot\mathbb{I}_d+2\mu\boldsymbol{\varepsilon} \]
def linear_elastic(eps):
    dim = eps.shape[0]
    lmbda, mu = sympy.symbols("lambda, mu", positive=True)
    return lmbda * sympy.trace(eps) * sympy.eye(dim) + 2 * mu * eps
FourthOrderTensor().from_operator(linear_elastic).as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}\lambda + 2 \mu & \lambda & \lambda & 0 & 0 & 0\\\lambda & \lambda + 2 \mu & \lambda & 0 & 0 & 0\\\lambda & \lambda & \lambda + 2 \mu & 0 & 0 & 0\\0 & 0 & 0 & 2 \mu & 0 & 0\\0 & 0 & 0 & 0 & 2 \mu & 0\\0 & 0 & 0 & 0 & 0 & 2 \mu\end{matrix}\right]\end{split}\]
FourthOrderTensor(dim=2).from_operator(linear_elastic).as_voigt()
\[\begin{split}\displaystyle \left[\begin{matrix}\lambda + 2 \mu & \lambda & 0\\\lambda & \lambda + 2 \mu & 0\\0 & 0 & \mu\end{matrix}\right]\end{split}\]

We can verify that the tensor intialized from Mandel representation provided by as_mandel and then converted to Voigt notation is the same as that directly initialized from the operator.

C = FourthOrderTensor().from_operator(linear_elastic)

FourthOrderTensor().from_mandel(C.as_mandel()).as_voigt() == C.as_voigt()
True

The same applies for Voigt notation.

FourthOrderTensor().from_voigt(C.as_voigt()).as_mandel() == C.as_mandel()
True

Conversion between array and the Voigt / Mandel / unsymmetric notations also works.

a_voigt = sympy.randMatrix(6)
a_voigt = a_voigt + a_voigt.T
a = FourthOrderTensor().from_voigt(a_voigt).as_array()
FourthOrderTensor().from_array(a).as_voigt() == a_voigt
True
a_mandel = sympy.randMatrix(6)
a_mandel = a_mandel + a_mandel.T
a = FourthOrderTensor().from_mandel(a_mandel).as_array()
FourthOrderTensor().from_array(a).as_mandel() == a_mandel
True
a_unsym = sympy.randMatrix(9)
a = FourthOrderTensor(symmetry=None).from_unsym(a_unsym).as_array()
FourthOrderTensor(symmetry=None).from_array(a).as_unsym() == a_unsym
True

Change of basis for second-order tensors#

A change of basis in \(\mathbb{R}^d\) (\(d\) is the spatial dimension dim) can be represented by an unsymmetric matrix \(\mathbf{R}\) whose columns contain the components of the new basis vectors in the old ones. For a second-order tensor \(\boldsymbol{\sigma}\) written as matrices, its representation in the new basis will be given by

\[ \boldsymbol{\sigma}'=\mathbf{R}^\mathsf{T}\boldsymbol{\sigma}\mathbf{R}. \]

The change of basis \(\boldsymbol{\sigma}\mapsto\boldsymbol{\sigma}'\) can be regarded as a fourth-order tensor operating on second-order ones.

def rotation(x):
    r = sympy.MatrixSymbol("R", x.shape[0], x.shape[0])  # generic unsymmetric matrix
    return r.T * x * r

For symmetric second-order tensors, Mandel notation can be used.

FourthOrderTensor().from_operator(rotation).as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}{R}_{0,0}^{2} & {R}_{1,0}^{2} & {R}_{2,0}^{2} & \sqrt{2} {R}_{0,0} {R}_{1,0} & \sqrt{2} {R}_{0,0} {R}_{2,0} & \sqrt{2} {R}_{1,0} {R}_{2,0}\\{R}_{0,1}^{2} & {R}_{1,1}^{2} & {R}_{2,1}^{2} & \sqrt{2} {R}_{0,1} {R}_{1,1} & \sqrt{2} {R}_{0,1} {R}_{2,1} & \sqrt{2} {R}_{1,1} {R}_{2,1}\\{R}_{0,2}^{2} & {R}_{1,2}^{2} & {R}_{2,2}^{2} & \sqrt{2} {R}_{0,2} {R}_{1,2} & \sqrt{2} {R}_{0,2} {R}_{2,2} & \sqrt{2} {R}_{1,2} {R}_{2,2}\\\sqrt{2} {R}_{0,0} {R}_{0,1} & \sqrt{2} {R}_{1,0} {R}_{1,1} & \sqrt{2} {R}_{2,0} {R}_{2,1} & {R}_{0,0} {R}_{1,1} + {R}_{0,1} {R}_{1,0} & {R}_{0,0} {R}_{2,1} + {R}_{0,1} {R}_{2,0} & {R}_{1,0} {R}_{2,1} + {R}_{1,1} {R}_{2,0}\\\sqrt{2} {R}_{0,0} {R}_{0,2} & \sqrt{2} {R}_{1,0} {R}_{1,2} & \sqrt{2} {R}_{2,0} {R}_{2,2} & {R}_{0,0} {R}_{1,2} + {R}_{0,2} {R}_{1,0} & {R}_{0,0} {R}_{2,2} + {R}_{0,2} {R}_{2,0} & {R}_{1,0} {R}_{2,2} + {R}_{1,2} {R}_{2,0}\\\sqrt{2} {R}_{0,1} {R}_{0,2} & \sqrt{2} {R}_{1,1} {R}_{1,2} & \sqrt{2} {R}_{2,1} {R}_{2,2} & {R}_{0,1} {R}_{1,2} + {R}_{0,2} {R}_{1,1} & {R}_{0,1} {R}_{2,2} + {R}_{0,2} {R}_{2,1} & {R}_{1,1} {R}_{2,2} + {R}_{1,2} {R}_{2,1}\end{matrix}\right]\end{split}\]

For unsymmetric second-order tensors, the unsymmetric representation can be used.

FourthOrderTensor().from_operator(rotation).as_unsym()
\[\begin{split}\displaystyle \left[\begin{matrix}{R}_{0,0}^{2} & {R}_{1,0}^{2} & {R}_{2,0}^{2} & {R}_{0,0} {R}_{1,0} & {R}_{0,0} {R}_{1,0} & {R}_{0,0} {R}_{2,0} & {R}_{0,0} {R}_{2,0} & {R}_{1,0} {R}_{2,0} & {R}_{1,0} {R}_{2,0}\\{R}_{0,1}^{2} & {R}_{1,1}^{2} & {R}_{2,1}^{2} & {R}_{0,1} {R}_{1,1} & {R}_{0,1} {R}_{1,1} & {R}_{0,1} {R}_{2,1} & {R}_{0,1} {R}_{2,1} & {R}_{1,1} {R}_{2,1} & {R}_{1,1} {R}_{2,1}\\{R}_{0,2}^{2} & {R}_{1,2}^{2} & {R}_{2,2}^{2} & {R}_{0,2} {R}_{1,2} & {R}_{0,2} {R}_{1,2} & {R}_{0,2} {R}_{2,2} & {R}_{0,2} {R}_{2,2} & {R}_{1,2} {R}_{2,2} & {R}_{1,2} {R}_{2,2}\\{R}_{0,0} {R}_{0,1} & {R}_{1,0} {R}_{1,1} & {R}_{2,0} {R}_{2,1} & {R}_{0,0} {R}_{1,1} & {R}_{0,1} {R}_{1,0} & {R}_{0,0} {R}_{2,1} & {R}_{0,1} {R}_{2,0} & {R}_{1,0} {R}_{2,1} & {R}_{1,1} {R}_{2,0}\\{R}_{0,0} {R}_{0,1} & {R}_{1,0} {R}_{1,1} & {R}_{2,0} {R}_{2,1} & {R}_{0,1} {R}_{1,0} & {R}_{0,0} {R}_{1,1} & {R}_{0,1} {R}_{2,0} & {R}_{0,0} {R}_{2,1} & {R}_{1,1} {R}_{2,0} & {R}_{1,0} {R}_{2,1}\\{R}_{0,0} {R}_{0,2} & {R}_{1,0} {R}_{1,2} & {R}_{2,0} {R}_{2,2} & {R}_{0,0} {R}_{1,2} & {R}_{0,2} {R}_{1,0} & {R}_{0,0} {R}_{2,2} & {R}_{0,2} {R}_{2,0} & {R}_{1,0} {R}_{2,2} & {R}_{1,2} {R}_{2,0}\\{R}_{0,0} {R}_{0,2} & {R}_{1,0} {R}_{1,2} & {R}_{2,0} {R}_{2,2} & {R}_{0,2} {R}_{1,0} & {R}_{0,0} {R}_{1,2} & {R}_{0,2} {R}_{2,0} & {R}_{0,0} {R}_{2,2} & {R}_{1,2} {R}_{2,0} & {R}_{1,0} {R}_{2,2}\\{R}_{0,1} {R}_{0,2} & {R}_{1,1} {R}_{1,2} & {R}_{2,1} {R}_{2,2} & {R}_{0,1} {R}_{1,2} & {R}_{0,2} {R}_{1,1} & {R}_{0,1} {R}_{2,2} & {R}_{0,2} {R}_{2,1} & {R}_{1,1} {R}_{2,2} & {R}_{1,2} {R}_{2,1}\\{R}_{0,1} {R}_{0,2} & {R}_{1,1} {R}_{1,2} & {R}_{2,1} {R}_{2,2} & {R}_{0,2} {R}_{1,1} & {R}_{0,1} {R}_{1,2} & {R}_{0,2} {R}_{2,1} & {R}_{0,1} {R}_{2,2} & {R}_{1,2} {R}_{2,1} & {R}_{1,1} {R}_{2,2}\end{matrix}\right]\end{split}\]

In 2-d, the rotation matrix can also be directly expressed by the theta variable.

def rotation(x):
    assert x.shape[0] == 2
    theta = sympy.symbols("theta")
    sin = sympy.sin(theta)
    cos = sympy.cos(theta)
    r = sympy.Matrix([[cos, -sin], [sin, cos]])
    return r.T * x * r


sympy.expand_trig(FourthOrderTensor(dim=2).from_operator(rotation).as_mandel())
\[\begin{split}\displaystyle \left[\begin{matrix}\cos^{2}{\left(\theta \right)} & \sin^{2}{\left(\theta \right)} & \sqrt{2} \sin{\left(\theta \right)} \cos{\left(\theta \right)}\\\sin^{2}{\left(\theta \right)} & \cos^{2}{\left(\theta \right)} & - \sqrt{2} \sin{\left(\theta \right)} \cos{\left(\theta \right)}\\- \sqrt{2} \sin{\left(\theta \right)} \cos{\left(\theta \right)} & \sqrt{2} \sin{\left(\theta \right)} \cos{\left(\theta \right)} & 2 \cos^{2}{\left(\theta \right)} - 1\end{matrix}\right]\end{split}\]

Acoustic tensor#

Given a unit vector \(\mathbf{n}\) in \(\mathbb{R}^d\) and the fourth-order stiffness tensor \(\mathbb{C}\), the following second-order acoustic tensor \(\mathbf{A}\) can be defined

\[ A_{ik}=\sum_{j=1}^d\sum_{l=1}^d n_j\cdot C_{ijkl}\cdot n_l. \]

This acoustic tensor can also be regarded as the output of a fourth-order tensor \(\mathbb{A}\) operating on the second-order tensor \(\mathbf{n}\otimes\mathbf{n}\).

\[ \mathbb{A}:\mathbf{n}\otimes\mathbf{n}\mapsto \mathbf{A},\quad A_{ik}=\sum_{j=1}^d\sum_{l=1}^d C_{ijkl}\cdot n_jn_l. \]

Assume that the stiffness tensor has major symmetry (for instance, under linear elasticity). It can thus be represented by Mandel notation.

C = sympy.Matrix(sympy.MatrixSymbol("C", 6, 6))
for i in range(C.shape[0]):
    for j in range(i):
        C[i, j] = C[j, i]
C = FourthOrderTensor().from_mandel(C)
C.as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}{C}_{0,0} & {C}_{0,1} & {C}_{0,2} & {C}_{0,3} & {C}_{0,4} & {C}_{0,5}\\{C}_{0,1} & {C}_{1,1} & {C}_{1,2} & {C}_{1,3} & {C}_{1,4} & {C}_{1,5}\\{C}_{0,2} & {C}_{1,2} & {C}_{2,2} & {C}_{2,3} & {C}_{2,4} & {C}_{2,5}\\{C}_{0,3} & {C}_{1,3} & {C}_{2,3} & {C}_{3,3} & {C}_{3,4} & {C}_{3,5}\\{C}_{0,4} & {C}_{1,4} & {C}_{2,4} & {C}_{3,4} & {C}_{4,4} & {C}_{4,5}\\{C}_{0,5} & {C}_{1,5} & {C}_{2,5} & {C}_{3,5} & {C}_{4,5} & {C}_{5,5}\end{matrix}\right]\end{split}\]

The fourth-order tensor \(\mathbb{A}\) can be defined as follows.

def acoustic_tensor(n_n):
    C_array = C.as_array()
    dim = 3
    A = sympy.zeros(dim)
    for i in range(dim):
        for k in range(dim):
            for j in range(dim):
                for l in range(dim):
                    A[i, k] += C_array[i, j, k, l] * n_n[j, l]
    return A

Its Mandel representation can be computed easily via as_mandel.

A = FourthOrderTensor().from_operator(acoustic_tensor)
A.as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}{C}_{0,0} & \frac{{C}_{3,3}}{2} & \frac{{C}_{4,4}}{2} & {C}_{0,3} & {C}_{0,4} & \frac{\sqrt{2} {C}_{3,4}}{2}\\\frac{{C}_{3,3}}{2} & {C}_{1,1} & \frac{{C}_{5,5}}{2} & {C}_{1,3} & \frac{\sqrt{2} {C}_{3,5}}{2} & {C}_{1,5}\\\frac{{C}_{4,4}}{2} & \frac{{C}_{5,5}}{2} & {C}_{2,2} & \frac{\sqrt{2} {C}_{4,5}}{2} & {C}_{2,4} & {C}_{2,5}\\{C}_{0,3} & {C}_{1,3} & \frac{\sqrt{2} {C}_{4,5}}{2} & {C}_{0,1} + \frac{{C}_{3,3}}{2} & \frac{\sqrt{2} {C}_{0,5}}{2} + \frac{{C}_{3,4}}{2} & \frac{\sqrt{2} {C}_{1,4}}{2} + \frac{{C}_{3,5}}{2}\\{C}_{0,4} & \frac{\sqrt{2} {C}_{3,5}}{2} & {C}_{2,4} & \frac{\sqrt{2} {C}_{0,5}}{2} + \frac{{C}_{3,4}}{2} & {C}_{0,2} + \frac{{C}_{4,4}}{2} & \frac{\sqrt{2} {C}_{2,3}}{2} + \frac{{C}_{4,5}}{2}\\\frac{\sqrt{2} {C}_{3,4}}{2} & {C}_{1,5} & {C}_{2,5} & \frac{\sqrt{2} {C}_{1,4}}{2} + \frac{{C}_{3,5}}{2} & \frac{\sqrt{2} {C}_{2,3}}{2} + \frac{{C}_{4,5}}{2} & {C}_{1,2} + \frac{{C}_{5,5}}{2}\end{matrix}\right]\end{split}\]

To compute the actual acoustic tensor, we need to define the second-order tensor \(\mathbf{n}\otimes\mathbf{n}\) as the outer product of \(\mathbf{n}\).

n = sympy.IndexedBase("n")
n = sympy.Matrix([n[i] for i in range(3)])
n_n = n * n.T
n_n = SecondOrderTensor().from_array(n_n)
n_n.as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}{n}_{0}^{2}\\{n}_{1}^{2}\\{n}_{2}^{2}\\\sqrt{2} {n}_{0} {n}_{1}\\\sqrt{2} {n}_{0} {n}_{2}\\\sqrt{2} {n}_{1} {n}_{2}\end{matrix}\right]\end{split}\]

The acoustic tensor can be obtained by performing matrix-vector multiplication between the Mandel representation of \(\mathbb{A}\) and that of \(\mathbf{n}\otimes\mathbf{n}\).

sympy.simplify(A.as_mandel() * n_n.as_mandel())
\[\begin{split}\displaystyle \left[\begin{matrix}{n}_{0}^{2} {C}_{0,0} + \sqrt{2} {n}_{0} {n}_{1} {C}_{0,3} + \sqrt{2} {n}_{0} {n}_{2} {C}_{0,4} + \frac{{n}_{1}^{2} {C}_{3,3}}{2} + {n}_{1} {n}_{2} {C}_{3,4} + \frac{{n}_{2}^{2} {C}_{4,4}}{2}\\\frac{{n}_{0}^{2} {C}_{3,3}}{2} + \sqrt{2} {n}_{0} {n}_{1} {C}_{1,3} + {n}_{0} {n}_{2} {C}_{3,5} + {n}_{1}^{2} {C}_{1,1} + \sqrt{2} {n}_{1} {n}_{2} {C}_{1,5} + \frac{{n}_{2}^{2} {C}_{5,5}}{2}\\\frac{{n}_{0}^{2} {C}_{4,4}}{2} + {n}_{0} {n}_{1} {C}_{4,5} + \sqrt{2} {n}_{0} {n}_{2} {C}_{2,4} + \frac{{n}_{1}^{2} {C}_{5,5}}{2} + \sqrt{2} {n}_{1} {n}_{2} {C}_{2,5} + {n}_{2}^{2} {C}_{2,2}\\\frac{\sqrt{2} \left(\sqrt{2} {C}_{0,5} + {C}_{3,4}\right) {n}_{0} {n}_{2}}{2} + \frac{\sqrt{2} \left(\sqrt{2} {C}_{1,4} + {C}_{3,5}\right) {n}_{1} {n}_{2}}{2} + \frac{\sqrt{2} \left(2 {C}_{0,1} + {C}_{3,3}\right) {n}_{0} {n}_{1}}{2} + {n}_{0}^{2} {C}_{0,3} + {n}_{1}^{2} {C}_{1,3} + \frac{\sqrt{2} {n}_{2}^{2} {C}_{4,5}}{2}\\\frac{\sqrt{2} \left(\sqrt{2} {C}_{0,5} + {C}_{3,4}\right) {n}_{0} {n}_{1}}{2} + \frac{\sqrt{2} \left(\sqrt{2} {C}_{2,3} + {C}_{4,5}\right) {n}_{1} {n}_{2}}{2} + \frac{\sqrt{2} \left(2 {C}_{0,2} + {C}_{4,4}\right) {n}_{0} {n}_{2}}{2} + {n}_{0}^{2} {C}_{0,4} + \frac{\sqrt{2} {n}_{1}^{2} {C}_{3,5}}{2} + {n}_{2}^{2} {C}_{2,4}\\\frac{\sqrt{2} \left(\sqrt{2} {C}_{1,4} + {C}_{3,5}\right) {n}_{0} {n}_{1}}{2} + \frac{\sqrt{2} \left(\sqrt{2} {C}_{2,3} + {C}_{4,5}\right) {n}_{0} {n}_{2}}{2} + \frac{\sqrt{2} \left(2 {C}_{1,2} + {C}_{5,5}\right) {n}_{1} {n}_{2}}{2} + \frac{\sqrt{2} {n}_{0}^{2} {C}_{3,4}}{2} + {n}_{1}^{2} {C}_{1,5} + {n}_{2}^{2} {C}_{2,5}\end{matrix}\right]\end{split}\]

Another possibility is to represent \(\mathbb{A}\) as linear operator, then applied to the array representation of \(\mathbf{n}\otimes\mathbf{n}\).

A.as_operator()(n_n.as_array())
\[\begin{split}\displaystyle \left[\begin{matrix}{n}_{0}^{2} {C}_{0,0} + \sqrt{2} {n}_{0} {n}_{1} {C}_{0,3} + \sqrt{2} {n}_{0} {n}_{2} {C}_{0,4} + \frac{{n}_{1}^{2} {C}_{3,3}}{2} + {n}_{1} {n}_{2} {C}_{3,4} + \frac{{n}_{2}^{2} {C}_{4,4}}{2} & \frac{\sqrt{2} {n}_{0}^{2} {C}_{0,3}}{2} + {n}_{0} {n}_{1} {C}_{0,1} + \frac{{n}_{0} {n}_{1} {C}_{3,3}}{2} + \frac{\sqrt{2} {n}_{0} {n}_{2} {C}_{0,5}}{2} + \frac{{n}_{0} {n}_{2} {C}_{3,4}}{2} + \frac{\sqrt{2} {n}_{1}^{2} {C}_{1,3}}{2} + \frac{\sqrt{2} {n}_{1} {n}_{2} {C}_{1,4}}{2} + \frac{{n}_{1} {n}_{2} {C}_{3,5}}{2} + \frac{{n}_{2}^{2} {C}_{4,5}}{2} & \frac{\sqrt{2} {n}_{0}^{2} {C}_{0,4}}{2} + \frac{\sqrt{2} {n}_{0} {n}_{1} {C}_{0,5}}{2} + \frac{{n}_{0} {n}_{1} {C}_{3,4}}{2} + {n}_{0} {n}_{2} {C}_{0,2} + \frac{{n}_{0} {n}_{2} {C}_{4,4}}{2} + \frac{{n}_{1}^{2} {C}_{3,5}}{2} + \frac{\sqrt{2} {n}_{1} {n}_{2} {C}_{2,3}}{2} + \frac{{n}_{1} {n}_{2} {C}_{4,5}}{2} + \frac{\sqrt{2} {n}_{2}^{2} {C}_{2,4}}{2}\\\frac{\sqrt{2} {n}_{0}^{2} {C}_{0,3}}{2} + {n}_{0} {n}_{1} {C}_{0,1} + \frac{{n}_{0} {n}_{1} {C}_{3,3}}{2} + \frac{\sqrt{2} {n}_{0} {n}_{2} {C}_{0,5}}{2} + \frac{{n}_{0} {n}_{2} {C}_{3,4}}{2} + \frac{\sqrt{2} {n}_{1}^{2} {C}_{1,3}}{2} + \frac{\sqrt{2} {n}_{1} {n}_{2} {C}_{1,4}}{2} + \frac{{n}_{1} {n}_{2} {C}_{3,5}}{2} + \frac{{n}_{2}^{2} {C}_{4,5}}{2} & \frac{{n}_{0}^{2} {C}_{3,3}}{2} + \sqrt{2} {n}_{0} {n}_{1} {C}_{1,3} + {n}_{0} {n}_{2} {C}_{3,5} + {n}_{1}^{2} {C}_{1,1} + \sqrt{2} {n}_{1} {n}_{2} {C}_{1,5} + \frac{{n}_{2}^{2} {C}_{5,5}}{2} & \frac{{n}_{0}^{2} {C}_{3,4}}{2} + \frac{\sqrt{2} {n}_{0} {n}_{1} {C}_{1,4}}{2} + \frac{{n}_{0} {n}_{1} {C}_{3,5}}{2} + \frac{\sqrt{2} {n}_{0} {n}_{2} {C}_{2,3}}{2} + \frac{{n}_{0} {n}_{2} {C}_{4,5}}{2} + \frac{\sqrt{2} {n}_{1}^{2} {C}_{1,5}}{2} + {n}_{1} {n}_{2} {C}_{1,2} + \frac{{n}_{1} {n}_{2} {C}_{5,5}}{2} + \frac{\sqrt{2} {n}_{2}^{2} {C}_{2,5}}{2}\\\frac{\sqrt{2} {n}_{0}^{2} {C}_{0,4}}{2} + \frac{\sqrt{2} {n}_{0} {n}_{1} {C}_{0,5}}{2} + \frac{{n}_{0} {n}_{1} {C}_{3,4}}{2} + {n}_{0} {n}_{2} {C}_{0,2} + \frac{{n}_{0} {n}_{2} {C}_{4,4}}{2} + \frac{{n}_{1}^{2} {C}_{3,5}}{2} + \frac{\sqrt{2} {n}_{1} {n}_{2} {C}_{2,3}}{2} + \frac{{n}_{1} {n}_{2} {C}_{4,5}}{2} + \frac{\sqrt{2} {n}_{2}^{2} {C}_{2,4}}{2} & \frac{{n}_{0}^{2} {C}_{3,4}}{2} + \frac{\sqrt{2} {n}_{0} {n}_{1} {C}_{1,4}}{2} + \frac{{n}_{0} {n}_{1} {C}_{3,5}}{2} + \frac{\sqrt{2} {n}_{0} {n}_{2} {C}_{2,3}}{2} + \frac{{n}_{0} {n}_{2} {C}_{4,5}}{2} + \frac{\sqrt{2} {n}_{1}^{2} {C}_{1,5}}{2} + {n}_{1} {n}_{2} {C}_{1,2} + \frac{{n}_{1} {n}_{2} {C}_{5,5}}{2} + \frac{\sqrt{2} {n}_{2}^{2} {C}_{2,5}}{2} & \frac{{n}_{0}^{2} {C}_{4,4}}{2} + {n}_{0} {n}_{1} {C}_{4,5} + \sqrt{2} {n}_{0} {n}_{2} {C}_{2,4} + \frac{{n}_{1}^{2} {C}_{5,5}}{2} + \sqrt{2} {n}_{1} {n}_{2} {C}_{2,5} + {n}_{2}^{2} {C}_{2,2}\end{matrix}\right]\end{split}\]

In the isotropic case, the previous linear_elastic operator can be used.

def acoustic_tensor_isotropic(n_n):
    C_array = FourthOrderTensor().from_operator(linear_elastic).as_array()
    dim = 3
    A = sympy.zeros(dim)
    for i in range(dim):
        for k in range(dim):
            for j in range(dim):
                for l in range(dim):
                    A[i, k] += C_array[i, j, k, l] * n_n[j, l]
    return A
A = FourthOrderTensor().from_operator(acoustic_tensor_isotropic)
A.as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}\lambda + 2 \mu & \mu & \mu & 0 & 0 & 0\\\mu & \lambda + 2 \mu & \mu & 0 & 0 & 0\\\mu & \mu & \lambda + 2 \mu & 0 & 0 & 0\\0 & 0 & 0 & \lambda + \mu & 0 & 0\\0 & 0 & 0 & 0 & \lambda + \mu & 0\\0 & 0 & 0 & 0 & 0 & \lambda + \mu\end{matrix}\right]\end{split}\]
sympy.simplify(A.as_mandel() * n_n.as_mandel())
\[\begin{split}\displaystyle \left[\begin{matrix}\mu {n}_{1}^{2} + \mu {n}_{2}^{2} + \left(\lambda + 2 \mu\right) {n}_{0}^{2}\\\mu {n}_{0}^{2} + \mu {n}_{2}^{2} + \left(\lambda + 2 \mu\right) {n}_{1}^{2}\\\mu {n}_{0}^{2} + \mu {n}_{1}^{2} + \left(\lambda + 2 \mu\right) {n}_{2}^{2}\\\sqrt{2} \left(\lambda + \mu\right) {n}_{0} {n}_{1}\\\sqrt{2} \left(\lambda + \mu\right) {n}_{0} {n}_{2}\\\sqrt{2} \left(\lambda + \mu\right) {n}_{1} {n}_{2}\end{matrix}\right]\end{split}\]
sympy.simplify(A.as_operator()(n_n.as_array()))
\[\begin{split}\displaystyle \left[\begin{matrix}\mu {n}_{1}^{2} + \mu {n}_{2}^{2} + \left(\lambda + 2 \mu\right) {n}_{0}^{2} & \left(\lambda + \mu\right) {n}_{0} {n}_{1} & \left(\lambda + \mu\right) {n}_{0} {n}_{2}\\\left(\lambda + \mu\right) {n}_{0} {n}_{1} & \mu {n}_{0}^{2} + \mu {n}_{2}^{2} + \left(\lambda + 2 \mu\right) {n}_{1}^{2} & \left(\lambda + \mu\right) {n}_{1} {n}_{2}\\\left(\lambda + \mu\right) {n}_{0} {n}_{2} & \left(\lambda + \mu\right) {n}_{1} {n}_{2} & \mu {n}_{0}^{2} + \mu {n}_{1}^{2} + \left(\lambda + 2 \mu\right) {n}_{2}^{2}\end{matrix}\right]\end{split}\]

The same procedure can be repeated for unsymmetric stiffness tensors, for instance for finite-strain applications.

C = sympy.Matrix(sympy.MatrixSymbol("C", 9, 9))
C = FourthOrderTensor().from_unsym(C)


def acoustic_tensor(n_n):
    C_array = C.as_array()
    dim = 3
    A = sympy.zeros(dim)
    for i in range(dim):
        for k in range(dim):
            for j in range(dim):
                for l in range(dim):
                    A[i, k] += C_array[i, j, k, l] * n_n[j, l]
    return A
A = FourthOrderTensor().from_operator(acoustic_tensor)
A.as_unsym()
\[\begin{split}\displaystyle \left[\begin{matrix}{C}_{0,0} & {C}_{3,3} & {C}_{5,5} & {C}_{0,3} & {C}_{3,0} & {C}_{0,5} & {C}_{5,0} & {C}_{3,5} & {C}_{5,3}\\{C}_{4,4} & {C}_{1,1} & {C}_{7,7} & {C}_{4,1} & {C}_{1,4} & {C}_{4,7} & {C}_{7,4} & {C}_{1,7} & {C}_{7,1}\\{C}_{6,6} & {C}_{8,8} & {C}_{2,2} & {C}_{6,8} & {C}_{8,6} & {C}_{6,2} & {C}_{2,6} & {C}_{8,2} & {C}_{2,8}\\{C}_{0,4} & {C}_{3,1} & {C}_{5,7} & {C}_{0,1} & {C}_{3,4} & {C}_{0,7} & {C}_{5,4} & {C}_{3,7} & {C}_{5,1}\\{C}_{4,0} & {C}_{1,3} & {C}_{7,5} & {C}_{4,3} & {C}_{1,0} & {C}_{4,5} & {C}_{7,0} & {C}_{1,5} & {C}_{7,3}\\{C}_{0,6} & {C}_{3,8} & {C}_{5,2} & {C}_{0,8} & {C}_{3,6} & {C}_{0,2} & {C}_{5,6} & {C}_{3,2} & {C}_{5,8}\\{C}_{6,0} & {C}_{8,3} & {C}_{2,5} & {C}_{6,3} & {C}_{8,0} & {C}_{6,5} & {C}_{2,0} & {C}_{8,5} & {C}_{2,3}\\{C}_{4,6} & {C}_{1,8} & {C}_{7,2} & {C}_{4,8} & {C}_{1,6} & {C}_{4,2} & {C}_{7,6} & {C}_{1,2} & {C}_{7,8}\\{C}_{6,4} & {C}_{8,1} & {C}_{2,7} & {C}_{6,1} & {C}_{8,4} & {C}_{6,7} & {C}_{2,4} & {C}_{8,7} & {C}_{2,1}\end{matrix}\right]\end{split}\]
sympy.simplify(A.as_unsym() * n_n.as_unsym())
\[\begin{split}\displaystyle \left[\begin{matrix}{n}_{0}^{2} {C}_{0,0} + {n}_{0} {n}_{1} {C}_{0,3} + {n}_{0} {n}_{1} {C}_{3,0} + {n}_{0} {n}_{2} {C}_{0,5} + {n}_{0} {n}_{2} {C}_{5,0} + {n}_{1}^{2} {C}_{3,3} + {n}_{1} {n}_{2} {C}_{3,5} + {n}_{1} {n}_{2} {C}_{5,3} + {n}_{2}^{2} {C}_{5,5}\\{n}_{0}^{2} {C}_{4,4} + {n}_{0} {n}_{1} {C}_{1,4} + {n}_{0} {n}_{1} {C}_{4,1} + {n}_{0} {n}_{2} {C}_{4,7} + {n}_{0} {n}_{2} {C}_{7,4} + {n}_{1}^{2} {C}_{1,1} + {n}_{1} {n}_{2} {C}_{1,7} + {n}_{1} {n}_{2} {C}_{7,1} + {n}_{2}^{2} {C}_{7,7}\\{n}_{0}^{2} {C}_{6,6} + {n}_{0} {n}_{1} {C}_{6,8} + {n}_{0} {n}_{1} {C}_{8,6} + {n}_{0} {n}_{2} {C}_{2,6} + {n}_{0} {n}_{2} {C}_{6,2} + {n}_{1}^{2} {C}_{8,8} + {n}_{1} {n}_{2} {C}_{2,8} + {n}_{1} {n}_{2} {C}_{8,2} + {n}_{2}^{2} {C}_{2,2}\\{n}_{0}^{2} {C}_{0,4} + {n}_{0} {n}_{1} {C}_{0,1} + {n}_{0} {n}_{1} {C}_{3,4} + {n}_{0} {n}_{2} {C}_{0,7} + {n}_{0} {n}_{2} {C}_{5,4} + {n}_{1}^{2} {C}_{3,1} + {n}_{1} {n}_{2} {C}_{3,7} + {n}_{1} {n}_{2} {C}_{5,1} + {n}_{2}^{2} {C}_{5,7}\\{n}_{0}^{2} {C}_{4,0} + {n}_{0} {n}_{1} {C}_{1,0} + {n}_{0} {n}_{1} {C}_{4,3} + {n}_{0} {n}_{2} {C}_{4,5} + {n}_{0} {n}_{2} {C}_{7,0} + {n}_{1}^{2} {C}_{1,3} + {n}_{1} {n}_{2} {C}_{1,5} + {n}_{1} {n}_{2} {C}_{7,3} + {n}_{2}^{2} {C}_{7,5}\\{n}_{0}^{2} {C}_{0,6} + {n}_{0} {n}_{1} {C}_{0,8} + {n}_{0} {n}_{1} {C}_{3,6} + {n}_{0} {n}_{2} {C}_{0,2} + {n}_{0} {n}_{2} {C}_{5,6} + {n}_{1}^{2} {C}_{3,8} + {n}_{1} {n}_{2} {C}_{3,2} + {n}_{1} {n}_{2} {C}_{5,8} + {n}_{2}^{2} {C}_{5,2}\\{n}_{0}^{2} {C}_{6,0} + {n}_{0} {n}_{1} {C}_{6,3} + {n}_{0} {n}_{1} {C}_{8,0} + {n}_{0} {n}_{2} {C}_{2,0} + {n}_{0} {n}_{2} {C}_{6,5} + {n}_{1}^{2} {C}_{8,3} + {n}_{1} {n}_{2} {C}_{2,3} + {n}_{1} {n}_{2} {C}_{8,5} + {n}_{2}^{2} {C}_{2,5}\\{n}_{0}^{2} {C}_{4,6} + {n}_{0} {n}_{1} {C}_{1,6} + {n}_{0} {n}_{1} {C}_{4,8} + {n}_{0} {n}_{2} {C}_{4,2} + {n}_{0} {n}_{2} {C}_{7,6} + {n}_{1}^{2} {C}_{1,8} + {n}_{1} {n}_{2} {C}_{1,2} + {n}_{1} {n}_{2} {C}_{7,8} + {n}_{2}^{2} {C}_{7,2}\\{n}_{0}^{2} {C}_{6,4} + {n}_{0} {n}_{1} {C}_{6,1} + {n}_{0} {n}_{1} {C}_{8,4} + {n}_{0} {n}_{2} {C}_{2,4} + {n}_{0} {n}_{2} {C}_{6,7} + {n}_{1}^{2} {C}_{8,1} + {n}_{1} {n}_{2} {C}_{2,1} + {n}_{1} {n}_{2} {C}_{8,7} + {n}_{2}^{2} {C}_{2,7}\end{matrix}\right]\end{split}\]
sympy.simplify(A.as_operator()(n_n.as_array()))
\[\begin{split}\displaystyle \left[\begin{matrix}{n}_{0}^{2} {C}_{0,0} + {n}_{0} {n}_{1} {C}_{0,3} + {n}_{0} {n}_{1} {C}_{3,0} + {n}_{0} {n}_{2} {C}_{0,5} + {n}_{0} {n}_{2} {C}_{5,0} + {n}_{1}^{2} {C}_{3,3} + {n}_{1} {n}_{2} {C}_{3,5} + {n}_{1} {n}_{2} {C}_{5,3} + {n}_{2}^{2} {C}_{5,5} & {n}_{0}^{2} {C}_{0,4} + {n}_{0} {n}_{1} {C}_{0,1} + {n}_{0} {n}_{1} {C}_{3,4} + {n}_{0} {n}_{2} {C}_{0,7} + {n}_{0} {n}_{2} {C}_{5,4} + {n}_{1}^{2} {C}_{3,1} + {n}_{1} {n}_{2} {C}_{3,7} + {n}_{1} {n}_{2} {C}_{5,1} + {n}_{2}^{2} {C}_{5,7} & {n}_{0}^{2} {C}_{0,6} + {n}_{0} {n}_{1} {C}_{0,8} + {n}_{0} {n}_{1} {C}_{3,6} + {n}_{0} {n}_{2} {C}_{0,2} + {n}_{0} {n}_{2} {C}_{5,6} + {n}_{1}^{2} {C}_{3,8} + {n}_{1} {n}_{2} {C}_{3,2} + {n}_{1} {n}_{2} {C}_{5,8} + {n}_{2}^{2} {C}_{5,2}\\{n}_{0}^{2} {C}_{4,0} + {n}_{0} {n}_{1} {C}_{1,0} + {n}_{0} {n}_{1} {C}_{4,3} + {n}_{0} {n}_{2} {C}_{4,5} + {n}_{0} {n}_{2} {C}_{7,0} + {n}_{1}^{2} {C}_{1,3} + {n}_{1} {n}_{2} {C}_{1,5} + {n}_{1} {n}_{2} {C}_{7,3} + {n}_{2}^{2} {C}_{7,5} & {n}_{0}^{2} {C}_{4,4} + {n}_{0} {n}_{1} {C}_{1,4} + {n}_{0} {n}_{1} {C}_{4,1} + {n}_{0} {n}_{2} {C}_{4,7} + {n}_{0} {n}_{2} {C}_{7,4} + {n}_{1}^{2} {C}_{1,1} + {n}_{1} {n}_{2} {C}_{1,7} + {n}_{1} {n}_{2} {C}_{7,1} + {n}_{2}^{2} {C}_{7,7} & {n}_{0}^{2} {C}_{4,6} + {n}_{0} {n}_{1} {C}_{1,6} + {n}_{0} {n}_{1} {C}_{4,8} + {n}_{0} {n}_{2} {C}_{4,2} + {n}_{0} {n}_{2} {C}_{7,6} + {n}_{1}^{2} {C}_{1,8} + {n}_{1} {n}_{2} {C}_{1,2} + {n}_{1} {n}_{2} {C}_{7,8} + {n}_{2}^{2} {C}_{7,2}\\{n}_{0}^{2} {C}_{6,0} + {n}_{0} {n}_{1} {C}_{6,3} + {n}_{0} {n}_{1} {C}_{8,0} + {n}_{0} {n}_{2} {C}_{2,0} + {n}_{0} {n}_{2} {C}_{6,5} + {n}_{1}^{2} {C}_{8,3} + {n}_{1} {n}_{2} {C}_{2,3} + {n}_{1} {n}_{2} {C}_{8,5} + {n}_{2}^{2} {C}_{2,5} & {n}_{0}^{2} {C}_{6,4} + {n}_{0} {n}_{1} {C}_{6,1} + {n}_{0} {n}_{1} {C}_{8,4} + {n}_{0} {n}_{2} {C}_{2,4} + {n}_{0} {n}_{2} {C}_{6,7} + {n}_{1}^{2} {C}_{8,1} + {n}_{1} {n}_{2} {C}_{2,1} + {n}_{1} {n}_{2} {C}_{8,7} + {n}_{2}^{2} {C}_{2,7} & {n}_{0}^{2} {C}_{6,6} + {n}_{0} {n}_{1} {C}_{6,8} + {n}_{0} {n}_{1} {C}_{8,6} + {n}_{0} {n}_{2} {C}_{2,6} + {n}_{0} {n}_{2} {C}_{6,2} + {n}_{1}^{2} {C}_{8,8} + {n}_{1} {n}_{2} {C}_{2,8} + {n}_{1} {n}_{2} {C}_{8,2} + {n}_{2}^{2} {C}_{2,2}\end{matrix}\right]\end{split}\]

Operations on fourth-order tensors#

Composition#

Fourth-order tensors can be composed as linear operators in the space of second-order tensors. For instance, the previous linear elasticity operator can be composed with the deviatoric operator, defined as follows

\[ (\mathbb{C}\circ \mathrm{dev})(\boldsymbol{\varepsilon})=\mathbb{C}\bigl(\mathrm{dev}(\boldsymbol{\varepsilon})\bigr). \]

Composition between FourthOrderTensor objects can be realized by the * operator.

The composed linear operator can then be represented by previous one of the previous representations.

dev = FourthOrderTensor(dim=2).from_operator(dev)
C = FourthOrderTensor(dim=2).from_operator(linear_elastic)
(C * dev).as_mandel()  # use * to compose two tensors
\[\begin{split}\displaystyle \left[\begin{matrix}\mu & - \mu & 0\\- \mu & \mu & 0\\0 & 0 & 2 \mu\end{matrix}\right]\end{split}\]

The obtained tensor \(\mathbb{C}\circ \mathrm{dev}\), as a FourthOrdereTensor object, can be converted to other representations.

(C * dev).as_unsym()
\[\begin{split}\displaystyle \left[\begin{matrix}\mu & - \mu & 0 & 0\\- \mu & \mu & 0 & 0\\0 & 0 & 2 \mu & 0\\0 & 0 & 0 & 2 \mu\end{matrix}\right]\end{split}\]

In general, operator composition is not commutative \(T\circ S\neq S\circ T\). However in this case, these two operators commute.

dev * C == C * dev
True

Note that two FourthOrderTensor objects can be compared directly using the equal = operator.

Inverse#

The inverse (if it exists) of a fourth-order tensor can be computed by the inv() method.

C.inv().as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}\frac{\lambda + 2 \mu}{4 \mu \left(\lambda + \mu\right)} & - \frac{\lambda}{4 \mu \left(\lambda + \mu\right)} & 0\\- \frac{\lambda}{4 \mu \left(\lambda + \mu\right)} & \frac{\lambda + 2 \mu}{4 \mu \left(\lambda + \mu\right)} & 0\\0 & 0 & \frac{1}{2 \mu}\end{matrix}\right]\end{split}\]

The inverse of the linear elasticity operator composed with itself gives well identity.

(C.inv() * C).as_mandel()
\[\begin{split}\displaystyle \left[\begin{matrix}1 & 0 & 0\\0 & 1 & 0\\0 & 0 & 1\end{matrix}\right]\end{split}\]
R_inv = FourthOrderTensor(dim=2).from_operator(rotation).inv().as_mandel()
R_inv
\[\begin{split}\displaystyle \left[\begin{matrix}\cos^{2}{\left(\theta \right)} & \sin^{2}{\left(\theta \right)} & - \frac{\sqrt{2} \sin{\left(2 \theta \right)}}{2}\\\sin^{2}{\left(\theta \right)} & \cos^{2}{\left(\theta \right)} & \frac{\sqrt{2} \sin{\left(2 \theta \right)}}{2}\\\frac{\sqrt{2} \sin{\left(2 \theta \right)}}{2} & - \frac{\sqrt{2} \sin{\left(2 \theta \right)}}{2} & \cos{\left(2 \theta \right)}\end{matrix}\right]\end{split}\]

We can check that the inverse of this rotation operator is well its transpose.

R_inv == FourthOrderTensor(dim=2).from_operator(rotation).as_mandel().T
True