Last Updated: May 5, 2019
The gradient of the velocity field is a strain-rate tensor field, that is, a second rank tensor field. It appears in the diffusion term of the Navier-Stokes equation.
A second rank tensor has nine components and can be expressed as a 3×3 matrix as shown in the above image.
In this blog post, I will pick out some typical tensor operations and give brief explanations of them with some usage examples in OpenFOAM.
Keywords
strain rate tensor, vorticity tensor, Q-criterion, Hodge dual
Gradient of a Vector Field | fvc::grad(u)
The gradient of a velocity vector \(\boldsymbol{u}\) returns a velocity gradient tensor (second rank tensor).
\begin{eqnarray}
\nabla \boldsymbol{u} &\equiv& \partial_{i} u_j \\
&=& \left(
\begin{matrix}
\partial u_1/\partial x_1 & \partial u_2/\partial x_1 & \partial u_3/\partial x_1 \\
\partial u_1/\partial x_2 & \partial u_2/\partial x_2 & \partial u_3/\partial x_2 \\
\partial u_1/\partial x_3 & \partial u_2/\partial x_3 & \partial u_3/\partial x_3
\end{matrix} \right)
\end{eqnarray}
Symmetric Part of a Second Rank Tensor | symm(T)
\begin{eqnarray}
{\rm symm}(\boldsymbol{T}) &\equiv& \frac{1}{2} (\boldsymbol{T} + \boldsymbol{T}^T) \\
&=& \frac{1}{2} \left(
\begin{matrix}
2T_{11} & T_{12} + T_{21} & T_{13} + T_{31} \\
T_{21} + T_{12} & 2T_{22} & T_{23} + T_{32} \\
T_{31} + T_{13} & T_{32} + T_{23} & 2T_{33}
\end{matrix} \right)
\end{eqnarray}
1 2 3 4 5 6 7 8 9 10 11 |
//- Return the symmetric part of a tensor template<class Cmpt> inline SymmTensor<Cmpt> symm(const Tensor<Cmpt>& t) { return SymmTensor<Cmpt> ( t.xx(), 0.5*(t.xy() + t.yx()), 0.5*(t.xz() + t.zx()), t.yy(), 0.5*(t.yz() + t.zy()), t.zz() ); } |
Twice the Symmetric Part of a Second Rank Tensor | twoSymm(T)
\begin{eqnarray}
{\rm twoSymm}(\boldsymbol{T}) &\equiv& \boldsymbol{T} + \boldsymbol{T}^T \\
&=& \left(
\begin{matrix}
2T_{11} & T_{12} + T_{21} & T_{13} + T_{31} \\
T_{21} + T_{12} & 2T_{22} & T_{23} + T_{32} \\
T_{31} + T_{13} & T_{32} + T_{23} & 2T_{33}
\end{matrix} \right)
\end{eqnarray}
1 2 3 4 5 6 7 8 9 10 11 |
//- Return twice the symmetric part of a tensor template<class Cmpt> inline SymmTensor<Cmpt> twoSymm(const Tensor<Cmpt>& t) { return SymmTensor<Cmpt> ( 2*t.xx(), (t.xy() + t.yx()), (t.xz() + t.zx()), 2*t.yy(), (t.yz() + t.zy()), 2*t.zz() ); } |
Skew-symmetric Part of a Second Rank Tensor | skew(T)
\begin{eqnarray}
{\rm skew}(\boldsymbol{T}) &\equiv& \frac{1}{2} (\boldsymbol{T} – \boldsymbol{T}^T) \\
&=& \frac{1}{2} \left(
\begin{matrix}
0 & T_{12} – T_{21} & T_{13} – T_{31} \\
T_{21} – T_{12} & 0 & T_{23} – T_{32} \\
T_{31} – T_{13} & T_{32} – T_{23} & 0
\end{matrix} \right)
\end{eqnarray}
1 2 3 4 5 6 7 8 9 10 11 |
//- Return the skew-symmetric part of a tensor template<class Cmpt> inline Tensor<Cmpt> skew(const Tensor<Cmpt>& t) { return Tensor<Cmpt> ( 0.0, 0.5*(t.xy() - t.yx()), 0.5*(t.xz() - t.zx()), 0.5*(t.yx() - t.xy()), 0.0, 0.5*(t.yz() - t.zy()), 0.5*(t.zx() - t.xz()), 0.5*(t.zy() - t.yz()), 0.0 ); } |
The symm and skew operations of the velocity gradient tensor field frequently appear in the source code. The following is a typical example.
1 2 3 4 5 6 |
tmp<volTensorField> tgradU(fvc::grad(U)); const volTensorField& gradU = tgradU(); volSymmTensorField b(dev(R)/(2*k_)); volSymmTensorField S(symm(gradU)); volTensorField Omega(skew(gradU)); |
In the above code, the symmetric and antisymmetric parts of the velocity gradient tensor \(\partial u_j/\partial x_i\) are defined as follows:
\begin{eqnarray}
S_{ij} &=& \frac{1}{2} \left( \frac{\partial u_j}{\partial x_i} + \frac{\partial u_i}{\partial x_j} \right), \\
\Omega_{ij} &=& \frac{1}{2} \left( \frac{\partial u_j}{\partial x_i} – \frac{\partial u_i}{\partial x_j} \right),
\end{eqnarray}
where \(S_{ij}\) is the strain rate tensor and \(-\Omega_{ij}\) is the vorticity (spin) tensor.
Hodge Dual | *T
\begin{equation}
*T = \left( T_{23},\;-T_{13},\;T_{12} \right)
\end{equation}
1 2 3 4 5 |
template<class Cmpt> inline Vector<Cmpt> operator*(const Tensor<Cmpt>& t) { return Vector<Cmpt>(t.yz(), -t.xz(), t.xy()); } |
The vorticity vector \(\boldsymbol{\omega}\) is calculated as the Hodge dual of the skew-symmetric part of the velocity gradient tensor.
\begin{eqnarray}
\boldsymbol{\omega} &=& 2 \times \left( * \Omega_{ij} \right) \\
&=& 2 \times \left( \Omega_{23},\;-\Omega_{13},\;\Omega_{12} \right) \\
&=& \left( \frac{\partial u_3}{\partial x_2}-\frac{\partial u_2}{\partial x_3},\;\frac{\partial u_1}{\partial x_3}-\frac{\partial u_3}{\partial x_1},\;\frac{\partial u_2}{\partial x_1}-\frac{\partial u_1}{\partial x_2} \right)
\end{eqnarray}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
template<class Type> tmp<GeometricField<Type, fvPatchField, volMesh>> curl ( const GeometricField<Type, fvPatchField, volMesh>& vf ) { word nameCurlVf = "curl(" + vf.name() + ')'; // Gausses theorem curl // tmp<GeometricField<Type, fvPatchField, volMesh>> tcurlVf = // fvc::surfaceIntegrate(vf.mesh().Sf() ^ fvc::interpolate(vf)); // Calculate curl as the Hodge dual of the skew-symmetric part of grad tmp<GeometricField<Type, fvPatchField, volMesh>> tcurlVf = 2.0*(*skew(fvc::grad(vf, nameCurlVf))); tcurlVf.ref().rename(nameCurlVf); return tcurlVf; } |
The operator * in front of the skew is the Hodge dual operator.
Inner Product of Two Second Rank Tensors | T & S
\begin{equation}
P_{ij} = T_{ik} S_{kj}
\end{equation}
1 2 3 4 5 6 7 8 9 10 11 |
tmp<volSymmTensorField> tS = dev(symm(gradU)); const volSymmTensorField& S = tS(); volScalarField W ( (2*sqrt(2.0))*((S&S)&&S) /( magS*S2 + dimensionedScalar("small", dimensionSet(0, 0, -3, 0, 0), SMALL) ) ); |
Double Inner Product of Two Second Rank Tensors | T && S
\begin{align}
s = T_{ij}S_{ij} &= T_{11}S_{11} + T_{12}S_{12} + T_{13}S_{13} \\
&+ T_{21}S_{21} + T_{22}S_{22} + T_{23}S_{23} \\
&+ T_{31}S_{31} + T_{32}S_{32} + T_{33}S_{33}
\end{align}
1 2 3 4 5 6 |
tmp<volTensorField> tgradU = fvc::grad(U); volScalarField::Internal G ( this->GName(), nut.v()*(dev(twoSymm(tgradU().v())) && tgradU().v()) ); |
Trace of a Second Rank Tensor | tr(T)
\begin{equation}
{\rm tr}(\boldsymbol{T}) \equiv T_{11} + T_{22} + T_{33} = \boldsymbol{T} {\rm \&}{\rm \&} \boldsymbol{I}
\end{equation}
1 2 3 4 5 6 |
//- Return the trace of a tensor template<class Cmpt> inline Cmpt tr(const Tensor<Cmpt>& t) { return t.xx() + t.yy() + t.zz(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
bool Foam::functionObjects::Q::calc() { if (foundObject<volVectorField>(fieldName_)) { const volVectorField& U = lookupObject<volVectorField>(fieldName_); const tmp<volTensorField> tgradU(fvc::grad(U)); const volTensorField& gradU = tgradU(); return store ( resultName_, 0.5*(sqr(tr(gradU)) - tr(((gradU) & (gradU)))) ); } else { return false; } } |
The Q-criterion can be used to identify vortex cores:
\begin{align}
Q &= \frac{1}{2} \left[ (\nabla \cdot \boldsymbol{u})^2 – {\rm tr}(\nabla \boldsymbol{u}^2)\right] \\
&= \frac{1}{2} \left[ (\nabla \cdot \boldsymbol{u})^2 + \Omega_{ij}\Omega_{ij} – S_{ij}S_{ij} \right].
\end{align}
For incompressible flows, it can be simplified as follows:
\begin{equation}
Q = \frac{1}{2} \left[ \Omega_{ij}\Omega_{ij} – S_{ij}S_{ij} \right].
\end{equation}
References
[1] OpenFOAM Programmer’s Guide
[2] CFD Direct | Tensor Mathematics