On this page

Transient problemsΒΆ

Transient problems are usually solved in FEniCS using a finite difference approximation of the time derivative. The time dimension can be discretized using constant discrete time intervals of length \(\triangle t\), and we look for solutions at the discrete times \(t = [0, \triangle t, 2\triangle t, ..., T-\triangle t, T] = k \triangle t\), for \(k=0, 1, 2, ..., N-1, N\), \(\triangle t = T/N\). The solutions at the \(N+1\) different timesteps are similarly written as \(u^k\). Using finite differences for the time derivative, a variational form of the heat equation reads

\[ \int_{\Omega} \frac{u^k - u^{k-1}}{\triangle t} v \mathrm{d}x = -\nu \int_{\Omega} \nabla u^{k-\frac{1}{2}} \cdot \nabla v \mathrm{d}x,\]

where the right hand side is computed at the midpoint between timesteps \(k\) and \(k-1\) using notation \(u^{k - \frac{1}{2}} = (u^k + u^{k-1})/2\). Note that when the solution is computed, we start at the initial condition at \(k=0\), where the initial condition \(u^{0}\) is known and \(u^1\) is unknown. When \(u^1\) is subsequently computed and known, we are ready to move on to the next solution \(u^2\) and so on. In other words, \(u^k\) is always the unknown we are trying to compute and \(u^{k-1}, u^{k-2}, ...\) are all considered to be known. In FEniCS the unknown \(u^k\) is represented in Forms as a TrialFunction, whereas all knowns are represented as Functions. A variational form for the heat equation in FEniCS may look like:

u = TrialFunction(V)
v = TestFunction(V)
u_ = Function(V)    # Known solution at k
u_1 = Function(V)   # Known solution at k-1
dt = Constant(0.1)
nu = 0.01
U = 0.5*(u+u_1)
F = inner(u - u_1, v)*dx + dt*nu*inner(grad(U), grad(v))*dx

The solution of the form must be placed inside a loop, advancing the solution forward in time, something like:

t = 0
while t < T:
    t += dt
    solve(lhs(F) == rhs(F), u_, bcs)
    # Advance solution to next timestep:
    u_1.vector()[:] = u_.vector()

The python functions lhs and rhs are used to extract bilinear (terms containing both trial- and testfunctions) and linear (terms containing only testfunction and no trialfunction) forms respectively. The last line of code copies all the values from u_ to u_1. Note that \(u^k\) is the unknown we look for at timestep \(k\). In the variational form \(u^k\) is represented as an unknown TrialFunction. However, when the variational form has been solved, the known solution can be found in the Function u_ and we are then finished with timestep \(k\). When we now move on to the next timestep, the solution we just found becomes the solution at the previous timestep, i.e., at \(k-1\). This is why we copy all values from u_ to u_1 as our final task in the time loop.