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=NoneMinor symmetry
symmetry="minor", which impliesa[i, j, k, l] = a[j, i, k, l] = a[i, j, l, k]. In practice, the expressions containinga[j, i, k, l]anda[i, j, l, k]withj > iandl > kwill be replaced bya[i, j, l, k].(Default) Major symmetry
symmetry="major", which additionally assumes thata[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
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
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()
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()
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()
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()
The spatial dimension can be specified by the dim parameter.
FourthOrderTensor(dim=2, symmetry="minor").as_mandel()
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()
FourthOrderTensor(dim=2, symmetry=None).as_unsym()
Array#
The underlying fourth-order array can also be shown via the as_array method.
FourthOrderTensor(dim=2).as_array()
By default, it is a ArraySymbol object. Its components can be shown via the as_explicit method.
FourthOrderTensor(dim=2).as_array().as_explicit()
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)
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)
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
a = FourthOrderTensor(dim).from_mandel(a_mandel) # initialize from Mandel notation
a.as_voigt() # now, represent as Voigt notation
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()
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()
By comparison, with Mandel notation the matrix identity is well obtained.
FourthOrderTensor(dim=2).from_operator(identity).as_mandel()
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()
FourthOrderTensor(dim=2).from_operator(dev).as_voigt()
Isotropic linear elasticity#
The fourth-order isotropic linear elastic stiffness tensor can be defined using two Lamé constants \(\lambda\) and \(\mu\)
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()
FourthOrderTensor(dim=2).from_operator(linear_elastic).as_voigt()
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
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()
For unsymmetric second-order tensors, the unsymmetric representation can be used.
FourthOrderTensor().from_operator(rotation).as_unsym()
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())
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
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}\).
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()
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()
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()
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())
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())
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()
sympy.simplify(A.as_mandel() * n_n.as_mandel())
sympy.simplify(A.as_operator()(n_n.as_array()))
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()
sympy.simplify(A.as_unsym() * n_n.as_unsym())
sympy.simplify(A.as_operator()(n_n.as_array()))
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
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
The obtained tensor \(\mathbb{C}\circ \mathrm{dev}\), as a FourthOrdereTensor object, can be converted to other representations.
(C * dev).as_unsym()
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()
The inverse of the linear elasticity operator composed with itself gives well identity.
(C.inv() * C).as_mandel()
R_inv = FourthOrderTensor(dim=2).from_operator(rotation).inv().as_mandel()
R_inv
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