![]() |
Reference documentation for deal.II version 9.6.0
|
This program was contributed by Narasimhan Swaminathan <n.swaminathan@iitm.ac.in>.
It comes without any warranty or support by its authors or the authors of deal.II.
This program is part of the deal.II code gallery and consists of the following files (click to inspect):
![]() | ![]() |
This application uses automatic differentiation to develop the residual and the jacobian for a simple transient non-linear heat transfer problem. The nonlinear solution is then done using TRILINOS NOX. The nonlinearity arises due to the dependence of thermal conductivity on the temperature. This example can be found in the following link.
The source codes are there in the source/
folder. Note that the geometry and its mesh are present in mesh/
folder. The output/
folder will contain the vtu files from the run. The folder include/
contains all the header files needed. Further, the file CMakeLists.txt
is added so that you can run the programme as
cmake . make all make run
In this example, we solve a simple transient nonlinear heat transfer equation. The nonlinearity is due to the temperature dependence of the thermal conductivity. Two main aspects covered by this example are (a) it develops the residual and the jacobian using automatic differentiation and (b) solves the nonlinear equations using TRILINOS NOX. The actual code contains the comments which will explain how these aspects are executed. Here, we give the full derivation and set up the equations. We also provide explanations to some of the functions important for this application.
We solve now a non-linear differential equation governing the heat transfer in a 2D domain using a combination of Automatic Differentiation and TRILINOS NOX in this example. The link for the original problem can be found here. We shall consider the finite element model of the following differential equation
\[ \rho C_p \frac{\partial T(x,y)}{\partial t} = \nabla \cdot k \nabla T + f \]
subjected to appropriate boundary conditions. For the sake of convenience, we will write the previous equation as
\[ \rho C_p \dot{T} = \left( k T_{,i} \right)_{,i} + f \]
The nonlinearity arises because the thermal conductivity is a function of the temperature \(T\). We will write down the functional form of this dependence later.
Please see this link for the actual domain over which the differential equation in solved. The .geo
and .msh
file for the domain can be found in the mesh
folder. The boundary conditions are as follows: Left end (domain id 1) is fixed at \(100^\circ\)C and the right end (domain id 3) has a outgoing flux of magnitude 10. All other material parameters are defined in file source/nonlinear_heat_cons_des.cc
The weak form of the equation over a typical finite element \(\Omega_e\) with boundary \(\Gamma_e\) is written as
\[ \int_{\Omega_e} w \left(\rho C_p \dot{T}- \left( k T_{,i} \right)_{,i} - f \right) dx dy = 0 \]
which becomes
\[ \int_{\Omega_e} w \rho C_p \dot{T}- \left( w \left( k T_{,i} \right)_{,i} + w f \right) dx dy = 0 \]
integrating by parts we get
\[ \int_{\Omega_e} w \rho C_p \dot{T} dx dy - \int_{\Omega_e} \left( -w_{,i} \left( k T_{,i} \right) + w f \right) dx dy -\int_{\Gamma_e} w kT_{,i}n_{i}ds^e = 0 \]
Clearly, \(k T_{,i}n_i\) is nothing but the heat flux along the boundary \(\Gamma_e\), which we write as \(q_{\Gamma^e}\). \(ds^e\) is an infinitesimal element along the boundary. Hence, we have
\[ \int_{\Omega_e} w \rho C_p \dot{T} dx dy - \int_{\Omega_e} \left( -w_{,i} \left( k T_{,i} \right) + w f \right) dx dy -\int_{\Gamma_e} w q_{\Gamma^e}ds^e = 0 \]
We now consider the approximation for \(T = \psi_J T_J\), where summation is implied over the repeated indices (Summation over all nodes in the finite element). \(\psi_J\) is the shape function. We hence get the finite element model taking ( \(w=\psi_I\)) as
\[ \dot{T}_J \int_{\Omega_e}\rho C_p \psi_I \psi_J dx dy + \int_{\Omega_e} \left( \psi_{I,i} \left( k \psi_{J,i}T_J \right) - \psi_I f \right) dx dy -\int_{\Gamma_e} \psi_I q_{\Gamma^e}ds^e = 0 \]
we note the \(k\) is now also a function of \(T\), hence we actually have
\[ \dot{T}_J \int_{\Omega_e}\rho C_p \psi_I \psi_J dx dy + \int_{\Omega_e} \left( \psi_{I,i} \left( k (\psi_PT_P) \psi_{J,i}T_J \right) - \psi_I f \right) dx dy -\int_{\Gamma_e} \psi_I q_{\Gamma^e}ds^e = 0 \]
calling \(\int_{\Gamma_e} \psi_I q_{\Gamma^e}ds^e\) as \(Q_I\), we have
\[ \dot{T}_J \int_{\Omega_e}\rho C_p \psi_I \psi_J dx dy + \int_{\Omega_e} \left( \psi_{I,i} \left( k (\psi_PT_P) \psi_{J,i}T_J \right) - \psi_I f \right) dx dy -Q_I = 0 \]
\[ \dot{T}_J \int_{\Omega_e}\rho C_p \psi_I \psi_J dx dy + \int_{\Omega_e} \left( \psi_{I,i} \left( k (\psi_PT_P) \psi_{J,i}T_J \right) - \psi_I f \right) dx dy -Q_I =0 \]
We write this in matrix form as
\[ M_{IJ}\dot{T}_J + L_I - Q_I=0 \]
\[ \underbrace{\left( \int_{\Omega_e}\rho C_p \psi_I \psi_J dx dy \right)}_{M_{IJ}} \dot{T}_J + \underbrace{\int_{\Omega_e} \left( \psi_{I,i} \left( k (\psi_PT_P) \psi_{J,i}T_J \right) - \psi_I f \right) dx dy}_{L_I} -Q_I = 0 \]
Since, this a time dependent problem, let us write the spatially discretized (matrix) form at times \(s\) and \(s+1\) as follows. This will allow us write the time marching schemes for the problem.
\begin{eqnarray} M_{IJ}^s\dot{T}_J^{s} + L_I^{s} - Q_I^{s}=0 \\ M_{IJ}^{s+1}\dot{T}_J^{s+1} + L_I^{s+1} - Q_I^{s+1}=0 \end{eqnarray}
We will consider the case where \(M_{IJ}\) and \(Q_I\) are both independent of time. Hence, we have
\begin{eqnarray} M_{IJ}\dot{T}_J^{s} + L_I^{s} - Q_I=0 \\ M_{IJ}\dot{T}_J^{s+1} + L_I^{s+1} - Q_I=0 \end{eqnarray}
Consider the following approximation for the time derivative
\[ \Delta t \left( \left(1-\alpha \right)\dot{T}_J^{s} + \alpha \dot{T}_J^{s+1} \right) = T_J^{s+1} - T_J^{s} \]
where \(\Delta t\) is the time step and \(\alpha\) is \(0<\alpha<1\). Multiplying the previous equation with \(M_{IJ}\) we get,
\[ \Delta t \left(1-\alpha \right) M_{IJ}\dot{T}_J^{s} + \Delta t \alpha M_{IJ}\dot{T}_J^{s+1} = M_{IJ}T_J^{s+1} - M_{IJ}T_J^{s} \]
combining terms properly,
\[ \Delta t \alpha M_{IJ}\dot{T}_J^{s+1} - M_{IJ}T_J^{s+1} = - M_{IJ}T_J^{s} - \Delta t \left(1-\alpha \right) M_{IJ}\dot{T}_J^{s} \]
We now use equations from above to get,
\[ \Delta t \alpha \left( -L_I^{s+1}+Q_I \right) - M_{IJ}T_J^{s+1} = - M_{IJ}T_J^{s} - \Delta t \left(1-\alpha \right) \left( -L_I^s+Q_I\right) \]
expanding
\[ - \alpha \Delta t L_I^{s+1} -M_{IJ}T_{J}^{s+1} = -M_{IJ}T_{J}^s + \Delta t \left( 1-\alpha \right) L_I^s-\Delta t \alpha Q_I -\Delta t \left(1-\alpha \right) Q_I \]
which gives
\[ \alpha \Delta t L_I^{s+1} + M_{IJ}T_{J}^{s+1} = M_{IJ}T_{J}^s - \Delta t \left( 1-\alpha \right) L_I^s+\Delta t \alpha Q_I +\Delta t \left(1-\alpha \right) Q_I \]
giving
\[ M_{IJ}T_{J}^{s+1} + \alpha \Delta t L_I^{s+1} = M_{IJ}T_{J}^s - \Delta t \left( 1-\alpha \right) L_I^s+ \Delta t Q_I \]
Now we have
\[ \boxed{ M_{IJ}T_{J}^{s+1} + \alpha \Delta t L_I^{s+1} - M_{IJ}T_{J}^s + \Delta t \left( 1-\alpha \right) L_I^s - \Delta t Q_I } \]
We want to find \(T_K\)s so that the previous equation is satisfied for every time step \(s+1\), in essence we want to find out \(T_K^{{s+1}}\). Consequently, for values of \(T_K^{{s+1}}\) which do not satisfy the equation, we will have a residual for each time step \(s+1\). Hence, we have
\[ \boxed{ M_{IJ}T_{J}^{s+1} + \alpha \Delta t L_I^{s+1} - M_{IJ}T_{J}^s + \Delta t \left( 1-\alpha \right) L_I^s - \Delta t Q_I = R_I^{s+1} } \]
is the residual at the \(s+1\) \(^{th}\) time step. After assembling, we need to find the \(T_{J}^{s+1}\) that essentially makes the assembled \(R_I^{s+1}\) go to zero. Numerically, we will require some norm of the residual to go to zero. Note that the residual we developed is local to the element. This will have to be assembled in the usual manner before solving. *We again note that \(T_K^{s}\) are all known from the previous time step \(s\).*
The solution (i.e. to find \(T_K^{{s+1}}\)) which make the residual to go to zero, can be found using the Newton Raphson's technique. For this we need to calculate the Jacobian or the Tangent stiffness matrix which involves calculating \(\frac{\partial R_I^{s+1}}{\partial T_{Q}^{s+1}}\). From the final equation above, we get,
\begin{eqnarray} J_{IQ}^{{s+1}}=\frac{\partial R_I^{s+1}}{\partial T_{Q}^{s+1}} = M_{IJ}\delta_{JQ} + \alpha \Delta t \frac{\partial L_I^{s+1}}{\partial T_Q}\\ J_{IQ}^{{s+1}}=\frac{\partial R_I^{s+1}}{\partial T_{Q}^{s+1}} = M_{IQ} + \alpha \Delta t \frac{\partial L_I^{s+1}}{\partial T_Q} \end{eqnarray}
This differentiation, is very simple for this problem. But it is not difficult to imagine situations, where this is terribly complicated and algebraically involved. Again, the terms which have a superfix \(s\) depend on values of \(T_H\) known from the previous step \(s\) and hence are known values and not variables; we do not need to differentiate the residual with respect to these known quantities. We know that
\[ \int_{\Omega_e} \left( \psi_{I,i} \left( k (\psi_PT_P) \psi_{J,i}T_J \right) - \psi_I f \right) dx dy = L_I^{s+1} \]
So,
\[ \frac{\partial L_I^{s+1}}{\partial T_Q} = \int_{\Omega_e} \left( \psi_{I,i}\psi_{J,i} \frac{\partial k}{\partial T_Q} T_J + \psi_{I,i}\psi_{J,i} k \delta_{JQ} \right) dx dy \]
if we now assume \(k = a+bT+cT^2\), with \(a,b\) and \(c\) being constants, we have,
\[ k = a + b (\psi_R T_R) + c \left( \psi_Y T_Y \right)^2 \]
then
\begin{eqnarray} \frac{\partial k}{\partial T_Q} = b\psi_R\delta_{RQ} + 2c (\psi_Y T_Y) \psi_Y\delta_{YQ} \\ \frac{\partial k}{\partial T_Q} = b\psi_Q + 2c (\psi_Y T_Y) \psi_Q \end{eqnarray}
and hence we get
\[ \frac{\partial L_I^{s+1}}{\partial T_Q} = \int_{\Omega_e} \left( \psi_{I,i} (\psi_{J,i}T_J) (b\psi_Q + 2c (\psi_Y T_Y) \psi_Q) + \psi_{I,i}\psi_{Q,i}(a + b (\psi_R T_R) + c \left( \psi_Y T_Y)^2 \right) \right) dx dy \]
Then, \(J_{IQ}^{{s+1}}\) can be written using the definition of \(J_{IQ}^{s+1}\). It is this calculation of the Jacobian which is often complicated and we intend to automate it using the /Automatic differentiation/ facility. These are implemented in the functions compute_residual()
and compute_jacobian()
. These functions are described in detail next.
The functions compute_residual()
and compute_jacobian()
compute the residuals and the jacobian, respectively. These are done with automatic differentiation from TRILINOS. For dealii tutorials on these, please see step-71 and step-72. The residual is computed in such a way that the a single line will actually differentiate it and calculate the jacobian given by the definition of \(J_{IQ}^{s+1}\). While the difference between compute_residual()
and compute_jacobian()
is minimal and could have been incorporated in a single function, we separate them in this implementation, because we want to use the nonlinear solver provided by TRILINOS (NOX) to actually perform the Newton Raphson iteration. For further details on this (Nonlinear solvers) aspect, please see step-77 of the dealii tutorial. We describe some aspects of the compute_residual()
and compute_jacobian()
here.
The compute_residual()
function actually takes in two arguments. evaluation_point
and residual
, both by reference. We will discuss this in some detail as we elaborate the function.
The following lines
should be fairly straightforward. The variable cell_rhs
holds the local (for an element or a cell) residual. Note that we use the FEFaceValues<2>
as we will need to apply the Neuman boundary conditions on the right hand side of the domain.
The lines below define the Number type we want to use to define our variables so that they are suitable for automatic differentiation. Here, we are using variables that will allow /automatic differentiation/. (The other option is symbolic differentiation using SYMENGINE package. Here we are using the automatic differentiation using SACADO.)
The following lines are once again straightforward to understand.
We only comment that the variable consol
holds the values of the variable converged_solution
at the gauss points of the current, active cell. Similarly, the variable consol_grad
holds the values of the gradients of the converged_solution
at the gauss points. The variable converged_solution
refers is solution at the previous time step (time step \(s\)).
In following lines the variables old_solution
and old_solution_grad
are crucial. They define the variables of the appropriate number type using which we will define our residual, so that it is suitable for the automatic differentiation. old_solution
is a solution variable of the appropriate number type with respect to which we need to define the residual and then differentiate it to get the Jacobian. For ease of understanding, one may consider this variable to be a special variable, that will look like this
\[ \text{old_solution} = \begin{bmatrix} \psi_i (\zeta_1,\eta_1) d_i\\ \psi_i (\zeta_2,\eta_2) d_i\\ \psi_i (\zeta_3,\eta_3) d_i\\ \psi_i (\zeta_4,\eta_4) d_i \end{bmatrix} \]
where \(\psi_i\) are the shape functions and \(d_i\) is some way of representing the primary variable in the problem (in this case the temperature). Could be viewed as something like a symbol, which could be used to define functions and then differentiated later. old_solution
holds the values in the corresponding gauss points and the repeated indices in the previous equation indicate summation over the nodes. Similarly, the variable old_solution_grad
holds the corresponding gradients.
The lines
are straightforward to understand. The vector residual
will hold the numerical value of the residual and is hence initialized to be zero.
The lines
should be clear. In the following line we inform how many independent and dependent variables we will have.
then, in the following line we tell the system, at which point it should numerically evaluate the residual. This is the point given by evaluation_point
, which will contain the solution that is getting iterated to find the actual solution for each time step.
Now the lines
the actual storage of the values of the shape functions and its derivatives at the gauss points is taking place in the variables old_solution
and old_solution_grad
.
In the lines
the values of the converged_solution
and its gradients get stored in the variables consol
and con_sol_grad
.
Then in the lines
the actual evaluation of the residual is taking place. Note that each intermediate variable used is of the ADNumberType
. Further, the residual is stored in a variable called residual_ad[i]
because it is also of the special type that will be suitable for differentiation. Note that the symbols in the residual equation in this document and the variables used in the code, map in the following manner. The residual is pasted below again
\[ \boxed{ M_{IJ}T_{J}^{s+1} + \alpha \Delta t L_I^{s+1} - M_{IJ}T_{J}^s + \Delta t \left( 1-\alpha \right) L_I^s - \Delta t Q_I = R_I^{s+1} } \]
MijTjcurr
MijTjprev
Licurr
Liprev
residual_ad[i]
then the following lines implement the Neumann boundary conditions
Now
actually tells the ad_helper
that this is the residual it should use, and the line
evaluates the residual at the appropriate point, which is the evaluation_point
. Then
are used to calculate the global residual
from the local cell_rhs
. Now residual
is numerical and not of the special number type. Finally, lines
are routine, in particular, it makes the global degrees of freedom, where the dirichlet boundary conditions are applied to be zero.
The compute_jacobian()
only takes in the evaluation_point
as an input and is identical to the compute_residual()
except for some minor modification. In particular the line
computes the actual jacobian by performing the automatic differentiation and evaluates it at the evaluation_point
. The remaining lines are routine and should be straightforward.
Consider that we have the equations
\begin{eqnarray} f_i^1(p_j^{{s+1}}, T_j^{{s+1}})=0 \\ f_i^2(p_j^{{s+1}}, T_j^{{s+1}})=0 \end{eqnarray}
are coupled non-linear algebraic equations in the variables \(p_j\) and \(T_j\). In the problem we are trying to solve, we only have one unknown, but this set of derivation clarifies what one should do for coupled problems as well.
We need to solve the set equations immediately above using Newton-Raphson's technique. Now suppose we have 4 noded linear element, with values as \(p_1^{{s+1}},p_2^{{s+1}},p_3^{{s+1}},p_4^{{s+1}}\) and \(T_1^{{s+1}},T_2^{{s+1}},T_3^{{s+1}},T_4^{{s+1}}\), then actually \(f_i^1\) and \(f_i^2\) are
\begin{eqnarray} f_i^1(p_1^{{s+1}},p_2^{{s+1}},p_3^{{s+1}},p_4^{{s+1}},T_1^{{s+1}},T_2^{{s+1}},T_3^{{s+1}},T_4^{{s+1}})=0 \\ f_i^2(p_1^{{s+1}},p_2^{{s+1}},p_3^{{s+1}},p_4^{{s+1}},T_1^{{s+1}},T_2^{{s+1}},T_3^{{s+1}},T_4^{{s+1}})=0 \end{eqnarray}
Also, note that \(i\) goes from 1 to 4 for \(f^1\) and from 1 to 4 for \(f^2\). For the Newton Raphson's technique, we have to find the Jacobian. This is written explicitly below for clear understanding.
\[ J_{pq}=- \begin{bmatrix} \frac{\partial f^1_1}{\partial p^{s+1}_1} & \frac{\partial f^1_1}{\partial p^{s+1}_2} & \frac{\partial f^1_1}{\partial p^{s+1}_3} & \frac{\partial f^1_1}{\partial p^{s+1}_4} & \frac{\partial f^1_1}{\partial T^{s+1}_1} & \frac{\partial f^1_1}{\partial T^{s+1}_2}&\frac{\partial f^1_1}{\partial T^{s+1}_3}&\frac{\partial f^1_1}{\partial T^{s+1}_4}\\ \frac{\partial f^1_2}{\partial p^{s+1}_1} & \frac{\partial f^1_2}{\partial p^{s+1}_2} & \frac{\partial f^1_2}{\partial p^{s+1}_3} & \frac{\partial f^1_2}{\partial p^{s+1}_4} & \frac{\partial f^1_2}{\partial T^{s+1}_1} & \frac{\partial f^1_2}{\partial T^{s+1}_2}&\frac{\partial f^1_2}{\partial T^{s+1}_3}&\frac{\partial f^1_2}{\partial T^{s+1}_4}\\ \frac{\partial f^1_3}{\partial p^{s+1}_1} & \frac{\partial f^1_3}{\partial p^{s+1}_2} & \frac{\partial f^1_3}{\partial p^{s+1}_3} & \frac{\partial f^1_3}{\partial p^{s+1}_4} & \frac{\partial f^1_3}{\partial T^{s+1}_1} & \frac{\partial f^1_3}{\partial T^{s+1}_2}&\frac{\partial f^1_3}{\partial T^{s+1}_3}&\frac{\partial f^1_3}{\partial T^{s+1}_4}\\ \frac{\partial f^1_4}{\partial p^{s+1}_1} & \frac{\partial f^1_4}{\partial p^{s+1}_2} & \frac{\partial f^1_4}{\partial p^{s+1}_3} & \frac{\partial f^1_4}{\partial p^{s+1}_4} & \frac{\partial f^1_4}{\partial T^{s+1}_1} & \frac{\partial f^1_4}{\partial T^{s+1}_2}&\frac{\partial f^1_4}{\partial T^{s+1}_3}&\frac{\partial f^1_4}{\partial T^{s+1}_4}\\ %================================================================ \frac{\partial f^2_1}{\partial p^{s+1}_1} & \frac{\partial f^2_1}{\partial p^{s+1}_2} & \frac{\partial f^2_1}{\partial p^{s+1}_3} & \frac{\partial f^2_1}{\partial p^{s+1}_4} & \frac{\partial f^2_1}{\partial T^{s+1}_1} & \frac{\partial f^2_1}{\partial T^{s+1}_2}&\frac{\partial f^2_1}{\partial T^{s+1}_3}&\frac{\partial f^2_1}{\partial T^{s+1}_4}\\ \frac{\partial f^2_2}{\partial p^{s+1}_1} & \frac{\partial f^2_2}{\partial p^{s+1}_2} & \frac{\partial f^2_2}{\partial p^{s+1}_3} & \frac{\partial f^2_2}{\partial p^{s+1}_4} & \frac{\partial f^2_2}{\partial T^{s+1}_1} & \frac{\partial f^2_2}{\partial T^{s+1}_2}&\frac{\partial f^2_2}{\partial T^{s+1}_3}&\frac{\partial f^2_2}{\partial T^{s+1}_4}\\ \frac{\partial f^2_3}{\partial p^{s+1}_1} & \frac{\partial f^2_3}{\partial p^{s+1}_2} & \frac{\partial f^2_3}{\partial p^{s+1}_3} & \frac{\partial f^2_3}{\partial p^{s+1}_4} & \frac{\partial f^2_3}{\partial T^{s+1}_1} & \frac{\partial f^2_3}{\partial T^{s+1}_2}&\frac{\partial f^2_3}{\partial T^{s+1}_3}&\frac{\partial f^2_3}{\partial T^{s+1}_4}\\ \frac{\partial f^2_4}{\partial p^{s+1}_1} & \frac{\partial f^2_4}{\partial p^{s+1}_2} & \frac{\partial f^2_4}{\partial p^{s+1}_3} & \frac{\partial f^2_4}{\partial p^{s+1}_4} & \frac{\partial f^2_4}{\partial T^{s+1}_1} & \frac{\partial f^2_4}{\partial T^{s+1}_2}&\frac{\partial f^2_4}{\partial T^{s+1}_3}&\frac{\partial f^2_4}{\partial T^{s+1}_4} \end{bmatrix} \]
We use the Jacobian evaluated at the values of the variables ( \(p_j^{{s+1}}\), \(T_j^{{s+1}}\)) at the /previous iteration/ (denoted by the variable present_solution
in the code) to obtain a new value of the required vector. That is
\[ \begin{bmatrix} p_1^{s+1}\\ p_2^{s+1}\\ p_3^{s+1}\\ p_4^{s+1}\\ T_1^{s+1}\\ T_2^{s+1}\\ T_3^{s+1}\\ T_4^{s+1} \end{bmatrix}^{k+1} = \begin{bmatrix} p_1^{s+1}\\ p_2^{s+1}\\ p_3^{s+1}\\ p_4^{s+1}\\ T_1^{s+1}\\ T_2^{s+1}\\ T_3^{s+1}\\ T_4^{s+1} \end{bmatrix}^{k}- \begin{bmatrix} \frac{\partial f^1_1}{\partial p^{s+1}_1} & \frac{\partial f^1_1}{\partial p^{s+1}_2} & \frac{\partial f^1_1}{\partial p^{s+1}_3} & \frac{\partial f^1_1}{\partial p^{s+1}_4} & \frac{\partial f^1_1}{\partial T^{s+1}_1} & \frac{\partial f^1_1}{\partial T^{s+1}_2}&\frac{\partial f^1_1}{\partial T^{s+1}_3}&\frac{\partial f^1_1}{\partial T^{s+1}_4}\\ \frac{\partial f^1_2}{\partial p^{s+1}_1} & \frac{\partial f^1_2}{\partial p^{s+1}_2} & \frac{\partial f^1_2}{\partial p^{s+1}_3} & \frac{\partial f^1_2}{\partial p^{s+1}_4} & \frac{\partial f^1_2}{\partial T^{s+1}_1} & \frac{\partial f^1_2}{\partial T^{s+1}_2}&\frac{\partial f^1_2}{\partial T^{s+1}_3}&\frac{\partial f^1_2}{\partial T^{s+1}_4}\\ \frac{\partial f^1_3}{\partial p^{s+1}_1} & \frac{\partial f^1_3}{\partial p^{s+1}_2} & \frac{\partial f^1_3}{\partial p^{s+1}_3} & \frac{\partial f^1_3}{\partial p^{s+1}_4} & \frac{\partial f^1_3}{\partial T^{s+1}_1} & \frac{\partial f^1_3}{\partial T^{s+1}_2}&\frac{\partial f^1_3}{\partial T^{s+1}_3}&\frac{\partial f^1_3}{\partial T^{s+1}_4}\\ \frac{\partial f^1_4}{\partial p^{s+1}_1} & \frac{\partial f^1_4}{\partial p^{s+1}_2} & \frac{\partial f^1_4}{\partial p^{s+1}_3} & \frac{\partial f^1_4}{\partial p^{s+1}_4} & \frac{\partial f^1_4}{\partial T^{s+1}_1} & \frac{\partial f^1_4}{\partial T^{s+1}_2}&\frac{\partial f^1_4}{\partial T^{s+1}_3}&\frac{\partial f^1_4}{\partial T^{s+1}_4}\\ %================================================================ \frac{\partial f^2_1}{\partial p^{s+1}_1} & \frac{\partial f^2_1}{\partial p^{s+1}_2} & \frac{\partial f^2_1}{\partial p^{s+1}_3} & \frac{\partial f^2_1}{\partial p^{s+1}_4} & \frac{\partial f^2_1}{\partial T^{s+1}_1} & \frac{\partial f^2_1}{\partial T^{s+1}_2}&\frac{\partial f^2_1}{\partial T^{s+1}_3}&\frac{\partial f^2_1}{\partial T^{s+1}_4}\\ \frac{\partial f^2_2}{\partial p^{s+1}_1} & \frac{\partial f^2_2}{\partial p^{s+1}_2} & \frac{\partial f^2_2}{\partial p^{s+1}_3} & \frac{\partial f^2_2}{\partial p^{s+1}_4} & \frac{\partial f^2_2}{\partial T^{s+1}_1} & \frac{\partial f^2_2}{\partial T^{s+1}_2}&\frac{\partial f^2_2}{\partial T^{s+1}_3}&\frac{\partial f^2_2}{\partial T^{s+1}_4}\\ \frac{\partial f^2_3}{\partial p^{s+1}_1} & \frac{\partial f^2_3}{\partial p^{s+1}_2} & \frac{\partial f^2_3}{\partial p^{s+1}_3} & \frac{\partial f^2_3}{\partial p^{s+1}_4} & \frac{\partial f^2_3}{\partial T^{s+1}_1} & \frac{\partial f^2_3}{\partial T^{s+1}_2}&\frac{\partial f^2_3}{\partial T^{s+1}_3}&\frac{\partial f^2_3}{\partial T^{s+1}_4}\\ \frac{\partial f^2_4}{\partial p^{s+1}_1} & \frac{\partial f^2_4}{\partial p^{s+1}_2} & \frac{\partial f^2_4}{\partial p^{s+1}_3} & \frac{\partial f^2_4}{\partial p^{s+1}_4} & \frac{\partial f^2_4}{\partial T^{s+1}_1} & \frac{\partial f^2_4}{\partial T^{s+1}_2}&\frac{\partial f^2_4}{\partial T^{s+1}_3}&\frac{\partial f^2_4}{\partial T^{s+1}_4} \end{bmatrix}^{-1} \begin{bmatrix} f_1^{1}\\ f_2^{1}\\ f_3^{1}\\ f_4^{1}\\ f_1^{2}\\ f_2^{2}\\ f_3^{2}\\ f_4^{2} \end{bmatrix}^{k} \]
The \(J_{pq}\) is evaluated with values obtained at the \(k^{th}\) iteration. To avoid taking the inverse, we solve the following, for the changes \(\Delta p\) and \(\Delta T\).
\[ \begin{bmatrix} \frac{\partial f^1_1}{\partial p^{s+1}_1} & \frac{\partial f^1_1}{\partial p^{s+1}_2} & \frac{\partial f^1_1}{\partial p^{s+1}_3} & \frac{\partial f^1_1}{\partial p^{s+1}_4} & \frac{\partial f^1_1}{\partial T^{s+1}_1} & \frac{\partial f^1_1}{\partial T^{s+1}_2}&\frac{\partial f^1_1}{\partial T^{s+1}_3}&\frac{\partial f^1_1}{\partial T^{s+1}_4}\\ \frac{\partial f^1_2}{\partial p^{s+1}_1} & \frac{\partial f^1_2}{\partial p^{s+1}_2} & \frac{\partial f^1_2}{\partial p^{s+1}_3} & \frac{\partial f^1_2}{\partial p^{s+1}_4} & \frac{\partial f^1_2}{\partial T^{s+1}_1} & \frac{\partial f^1_2}{\partial T^{s+1}_2}&\frac{\partial f^1_2}{\partial T^{s+1}_3}&\frac{\partial f^1_2}{\partial T^{s+1}_4}\\ \frac{\partial f^1_3}{\partial p^{s+1}_1} & \frac{\partial f^1_3}{\partial p^{s+1}_2} & \frac{\partial f^1_3}{\partial p^{s+1}_3} & \frac{\partial f^1_3}{\partial p^{s+1}_4} & \frac{\partial f^1_3}{\partial T^{s+1}_1} & \frac{\partial f^1_3}{\partial T^{s+1}_2}&\frac{\partial f^1_3}{\partial T^{s+1}_3}&\frac{\partial f^1_3}{\partial T^{s+1}_4}\\ \frac{\partial f^1_4}{\partial p^{s+1}_1} & \frac{\partial f^1_4}{\partial p^{s+1}_2} & \frac{\partial f^1_4}{\partial p^{s+1}_3} & \frac{\partial f^1_4}{\partial p^{s+1}_4} & \frac{\partial f^1_4}{\partial T^{s+1}_1} & \frac{\partial f^1_4}{\partial T^{s+1}_2}&\frac{\partial f^1_4}{\partial T^{s+1}_3}&\frac{\partial f^1_4}{\partial T^{s+1}_4}\\ %================================================================ \frac{\partial f^2_1}{\partial p^{s+1}_1} & \frac{\partial f^2_1}{\partial p^{s+1}_2} & \frac{\partial f^2_1}{\partial p^{s+1}_3} & \frac{\partial f^2_1}{\partial p^{s+1}_4} & \frac{\partial f^2_1}{\partial T^{s+1}_1} & \frac{\partial f^2_1}{\partial T^{s+1}_2}&\frac{\partial f^2_1}{\partial T^{s+1}_3}&\frac{\partial f^2_1}{\partial T^{s+1}_4}\\ \frac{\partial f^2_2}{\partial p^{s+1}_1} & \frac{\partial f^2_2}{\partial p^{s+1}_2} & \frac{\partial f^2_2}{\partial p^{s+1}_3} & \frac{\partial f^2_2}{\partial p^{s+1}_4} & \frac{\partial f^2_2}{\partial T^{s+1}_1} & \frac{\partial f^2_2}{\partial T^{s+1}_2}&\frac{\partial f^2_2}{\partial T^{s+1}_3}&\frac{\partial f^2_2}{\partial T^{s+1}_4}\\ \frac{\partial f^2_3}{\partial p^{s+1}_1} & \frac{\partial f^2_3}{\partial p^{s+1}_2} & \frac{\partial f^2_3}{\partial p^{s+1}_3} & \frac{\partial f^2_3}{\partial p^{s+1}_4} & \frac{\partial f^2_3}{\partial T^{s+1}_1} & \frac{\partial f^2_3}{\partial T^{s+1}_2}&\frac{\partial f^2_3}{\partial T^{s+1}_3}&\frac{\partial f^2_3}{\partial T^{s+1}_4}\\ \frac{\partial f^2_4}{\partial p^{s+1}_1} & \frac{\partial f^2_4}{\partial p^{s+1}_2} & \frac{\partial f^2_4}{\partial p^{s+1}_3} & \frac{\partial f^2_4}{\partial p^{s+1}_4} & \frac{\partial f^2_4}{\partial T^{s+1}_1} & \frac{\partial f^2_4}{\partial T^{s+1}_2}&\frac{\partial f^2_4}{\partial T^{s+1}_3}&\frac{\partial f^2_4}{\partial T^{s+1}_4} \end{bmatrix} \begin{bmatrix} \Delta p_1^{s+1}\\ \Delta p_2^{s+1}\\ \Delta p_3^{s+1}\\ \Delta p_4^{s+1}\\ \Delta T_1^{s+1}\\ \Delta T_2^{s+1}\\ \Delta T_3^{s+1}\\ \Delta T_4^{s+1} \end{bmatrix}= \begin{bmatrix} f_1^{1}\\ f_2^{1}\\ f_3^{1}\\ f_4^{1}\\ f_1^{2}\\ f_2^{2}\\ f_3^{2}\\ f_4^{2} \end{bmatrix}^{k} \]
This is written as
\[ J_{pq}\{\Delta solution\}^{k+1}=\{function\}^{k} \]
Then add this to the value of
\[ \begin{bmatrix} p_1^{s+1}\\ p_2^{s+1}\\ p_3^{s+1}\\ p_4^{s+1}\\ T_1^{s+1}\\ T_2^{s+1}\\ T_3^{s+1}\\ T_4^{s+1} \end{bmatrix}^{k} \]
We keep doing this, until the \(L_2\)-error of the vector is small. Determining whether the norm is small can be done using either
\[ ||(function)^k||<\delta^0 ||function^0|| \]
or
\[ ||(function)^k||<tol \]
where \(tol\) is some small number. In the code, we use the second of these two choices. The final converged solution in the one that satisfies the set of equations and is called by the variable converged_solution
in the code. This iterative process is carried out for every time step. The variable present_solution
is given the value of converged_solution
at the beginning of the iteration of a given time step, so that it serves as a better initial guess for the nonlinear set of equations for the next time step.
The function which implements the nonlinear solver is the run()
function for every time step. In particular the lines
Here, one needs to give how the residual is to be calculated and at what point, how the jacobian is to be calculated and at what point and finally, how to solve the linear system occurring during each iteration. These are given as lambda
functions.
The lines
setup the solver and give any basic data needed, such as the tolerance to converge or the maximum number of iterations it can try.
The lines
essentially, defines the function from where the residual will be calculated. Notice here that the compute_residual()
function is called with evaluation_point
and residual
.
Similarly, the lines,
define the function from where the jacobian will be calculated. Note that it takes in a variable current_u
which will be the evaluation_point
for the computing the jacobian. Thus, the actual points for evaluating the jacobian and the residual need not be the same.
The lines
tells the solution needs to be done with the jacobian using the function solve()
.
Then the line
actually calls the nonlinear solver.
For details as to how these lambda functions work together, please see step-77 and also the file tests/trilinos/step-77-with-nox.cc
. Overall, the variable present_solution
is presented as an initial guess to the non-linear solver, which performs the iteration and gives the final converged solution in the same variable. Hence, this will be the converged solution for the current step and this assignment happens in the following line
. The variable present_solution
is assigned the value of converged_solution
from the previous time step to serve as an initial guess. This is done in line 45.
The results are essentially the time evolution of the temperature throughout the domain. The first of the pictures below shows the temperature distribution at the final step, i.e. at time \(t=5\). This should be very similar to the figure at the bottom on the page here. We also plot the time evolution of the temperature at a point close to the right edge of the domain indicated by the small magenta dot (close to \((0.49, 0.12)\)) in the second of the pictures below. This is also similar to the second figure at the bottom of this page. There could be minor differences due to the choice of the point. Further, note that, we have plotted in the second of the pictures below the temperature as a function of time steps instead of time. Since the \(\Delta t\) chosen is 0.1, 50 steps maps to \(t=5\) as in the link.
Contour plot of the temperature at the final step
Evolution of temperature at a point close to the right edge \(\approx (0.49, 0.12)\)
In closing, we give some ideas as to how the residuals and the jacobians are actually calculated in a finite element setting.
For solving the linear equation every iteration, we have to evaluate the right hand side functions ( \(f_i^1\) and \(f_i^2\)) at the values \(p\) and \(T\) had at their previous iteration. For instance, in the current problem consider the final equation above (we only have only one function \(f_I\) in this case), which is
\[ M_{IJ}T_{J}^{s+1} + \alpha \Delta t L_I^{s+1} - M_{IJ}T_{J}^s + \Delta t \left( 1-\alpha \right) L_I^s - \Delta t Q_I = R_I^{s+1} \]
Each term is written in the following manner. We write it for only one term to illustrate the details. Consider \(\alpha \Delta t L_I^{s+1} \)
\[ \alpha \Delta t \int_{\Omega_e} \left( \psi_{I,i} \left( k (\psi_PT_P) \psi_{J,i}T_J \right) - \psi_I f \right) dx dy = L_I^{s+1} \]
to be specific
\[ \alpha \Delta t \int_{\Omega_e} \left( \psi_{I,i} \left( k (\psi_PT_P^{s+1}) \psi_{J,i}T_J^{s+1} \right) - \psi_I f \right) dx dy = L_I^{s+1} \]
again, the term \(\psi_{J,i} T_J^{{s+1}}\) is nothing but the value of gradient of \(T^{{s+1}}\) at whatever point the gradient of \(\psi_{J}\) is evaluated in. We write this gradient as \(\nabla T^{{s+1}}(x,y)\). Similarly, \(\psi_PT_P^{{s+1}}\) is the value of the temperature at the point where \(\psi_P\) is evaluated and we call it \(T^{{s+1}}\). While evaluating these terms to calculate the residual, we will use Gauss quadrature and this evaluation of this integral would become something like
\[ L_I^{{s+1}} = \Delta t \alpha \sum_{h} \nabla \psi_I (\xi_h,\eta_h) \cdot k(T^{{s+1}}) \nabla T^{{s+1}}(\xi_h,\eta_h) w_hJ_h \]
where \(h\) runs over the total number of quadrature points in the finite element and \(w_hJ_h\) takes care of appropriate weights needed and other corrections needed to due to transforming the finite element onto a master element (See any finite element book for details). All other terms can also be calculated in a similar manner to obtain the per-element residual from the first equation in this section. This has to assembled over all finite elements to get the global residual.
Similar evaluations have to be done to obtain the Jacobian as well. In the current case, the per element Jacobian is given by
\[ J^e_{IQ} = \frac{\partial R_I^{s+1}}{\partial T_{Q}^{s+1}} = M_{IQ} + \alpha \Delta t \frac{\partial L_I^{s+1}}{\partial T_Q} \]
which is written as
\[ J_{IQ}^e = \int_{\Omega_e}\rho C_p \psi_I \psi_J dx dy + \alpha \Delta t \int_{\Omega_e} \left( \psi_{I,i} (\psi_{J,i}T_J) (b\psi_Q + 2c (\psi_Y T_Y) \psi_Q) + \psi_{I,i}\psi_{Q,i}(a + b (\psi_R T_R) + c \left( \psi_Y T_Y) \right)^2 \right) dx dy \]
which are evaluated using gauss quadrature by summing the functions after evaluation at the gauss points. Once again, the terms \(\psi_{J,i}T_J\) and \(\psi_R T_R\) are the values of the gradients of the temperature and the temperature itself at the Gauss points.
To understand this application, step-71, step-72 and step-77 are needed.
The following copyright notice applies to the mesh files in this directory:
These are some of the header files for this programme. Most of them are common from previous steps.
Automatic differentiation is carried out with TRILINOS Automatic differentiation scheme/ This is invoked with the following include.
We use Trilinos wrappers based NOX to solve the non-linear equations.
Returns the initial values.
nonlinear_heat nlheat; double total_time = nlheat.tot_time; return this->get_time() * 100.0/total_time;