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
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 Form
s as a TrialFunction
, whereas all knowns are represented as Function
s. 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.