Viscoelastic Oldroyd-B model – rolling asphalt¶
Computation in the time varying domain¶
We show how to solve problem in time varying domains. We transform the equations describing the viscoelastic Oldroyd-B model from Eulerian description where the domain deforms into a description with a fixed domain. In order to do this we add a new variable deformation \(\hat{\mathbf{u}}\). The mapping \(\hat{\varphi}\) maps fixed domain \(\Omega_{\chi}\) into the deforming domain \(\Omega_{x}\), see the Figure below.

There are two popular transformations: 1. The fixed domain corresponds to the Lagrangian description where all points in the domain are material points and \(\mathbf{u}\) is the physical deformation. 2. The so-called arbitrary Lagrangian-Eulerian (ALE) formulation where deformation \(\hat{\mathbf{u}}\) is arbitrary with the restriction that the envelop of the deformed remains the same.
Lagrangian formulation¶
The computational mesh is identified with the reference Lagrangian configuration, so the mapping \(\varphi\) is defined as
We define the following kinematic quantities. The velocity \(\mathbf{v}\)
the deformation gradient \(\mathbb{F}\) and its Jacobian \(J\) by
The aim to transform the weak form of the governing equations from \(\Omega_x\) to \(\Omega_X\)
First we check, how the spatial derivative transforms
Next, we check the material time derivative of scalar \(s\)
Finally, the integrals over \(\Omega_x\) are transformed to the integrals over \(\Omega_X\) using the integral substitution theorem. The Piola identity in the form
is employed in the integral
The weak formulation in the Lagrangian formulation then reads
To close the system of equations we provide the last equation
describing that all points are material. The Lagrangian formulation works well if the changes in the domain are not too big. The main problem appears because all points are material and for example a vortex in the flow can damage the virtual mesh.
Arbitrary Lagrangian-Eulerian formulation¶
Here we identify the computational domain with a new domain \(\Omega_\chi\), that is mapped by \(\hat{\varphi}\) to the Eulerian domain \(\Omega_x\), i.e.
where \(\hat{\mathbf{u}}\) is an arbitrary deformation for which we require that on the boundary of the domain it preserves the envelope (we can assume for example that the points on the boundary are amterial poingts, inside the domain \(\Omega_\chi\) we just require a smooth unique solution, for simplicity we can solve a simple Laplace equation, i.e.
As before, we define the deformation gradient \(\hat{\mathbb{F}}\) and its Jacobian \(\hat{J}\) by
Using the same procedure as in the Lagrangian transformation we compute how the gradient and the material derivative transform, i.e.
and the time derivative of scalar \(s\) reads
The last relation can be used to express the material time derivative of the scalar \(s\)
By transforming the weak form in the Eulerian setting we arrive to
The set of equations is closed with the Laplace equation for \(\hat{\mathbf{u}}\).
The divergence theorem is applied in the second term of the transformed balance of linear momentum
and the last term is used to prescribe the Neumann boundary condition.
Implementation of the boundary condition – Nitsche method¶
To implement the solution of the laplace equation for \(\hat{\mathbf{u}}\) together with the Dirichlet boundary condition
we use the Nitsche method that treats the Dirichlet boundary condition in the weak sense.
The time derivative is approximated using the backward Euler time scheme with constant time-step \(\Delta t\), thus on \(n-\)th time level we solve the following problem
Equivalently, the boundary condition can be written in the form
In the Nitsche method we multiply the laplace equation with the test function \(\mathbf{t}\), integrate it over \(\Omega_\chi\) and use the divergence theorem
Next, we take the relation on the boundary \(\partial\Omega_\chi\), multiply it by the test function \((\nabla_\chi\mathbf{t})\mathbf{n}\), integrate over \(\partial\Omega_\chi\) and add it do the previous equation, finally we add a classical penalty term
where \(\beta\) is the penalty parameter and \(h\) is the edge length on \(\partial\Omega_\chi\). This type of Nitsche method is called the non-symmetric Nitsche method (Burman and Hansbo 2012), its advantage is that it is stable for all parameters \(\beta\geq0\).
Problem description¶
We compute the problem of rolling a viscoelastic material (asphalt). Let us consider the rectangular piece of material, 3m wide and 0.5m high. oldroyd-B model is used with the material parameters \(G=10\,\)kPa, \(\mu_1=10\,\)kPa s and \(\mu_2=100\,\)Pa s. The problem is depicted in the Figure below.

The material is on the ground where it can fully slip in the \(x\)-direction, but it can not move in the \(y\)-direction. All other sides of the rectangle can freely move. At \(t = 0\) the material is pushed at the top with a constant normal stress \(T_{yy} = −5\,\)kPa. The force is applied on the constant area \(l = 50\,\)cm, which moves with the velocity \(0.4\,\)m/s from the left to the right and then back to the left, i.e. the material is rolled forward and back.
FEniCS implementation¶
Import dolfin (FEniCS backend), matplotlib.pyplot (for plots), numpy (for arrays) and time (for time benchmarking).
[1]:
import dolfin as df
import matplotlib.pyplot as plt
import numpy as np
from time import time
Create mesh.
[2]:
L = 3.0 # Length
H = 0.5 # Height
nx = 60
ny = 10
mesh = df.RectangleMesh(df.Point(0.0, 0.0), df.Point(L, H), nx, ny, "crossed")
df.plot(mesh)
plt.show()
Identify boundaries.
[3]:
# define boundary as a class
class Bottom(df.SubDomain):
def inside(self, x, on_boundary):
return on_boundary and df.near(x[1], 0)
bottom = Bottom()
class Top(df.SubDomain):
def inside(self, x, on_boundary):
return on_boundary and df.near(x[1], H)
top = Top()
class Lateral(df.SubDomain):
def inside(self, x, on_boundary):
return on_boundary and (df.near(x[0], 0) or df.near(x[0], L))
lateral = Lateral()
# mark boundary parts
bndry = df.MeshFunction('size_t', mesh, mesh.topology().dim()-1, 0)
bottom.mark(bndry, 1)
top.mark(bndry, 2)
lateral.mark(bndry, 3)
ds = df.Measure("ds", subdomain_data=bndry)
Define mixed function space for pressure \(p\), velocity \(\mathbf{v}\) and extra stress tensor \(\mathbb{B}\). In this example, we use inf-sup stable Taylor-Hood elements for velocity and pressure, which are quadratic in velocity and linear in pressure; and linear in extra stress tensor.
[4]:
Ev = df.VectorElement("CG", mesh.ufl_cell(), 2) # 2 = quadratic elements
Ep = df.FiniteElement("CG", mesh.ufl_cell(), 1) # 1 = linear elements
E = df.MixedElement([Ev, Ev, Ep, Ep, Ep, Ep])
W = df.FunctionSpace(mesh, E)
print("Problem size: {0:d}".format(W.dim()))
Calling FFC just-in-time (JIT) compiler, this may take some time.
Problem size: 24848
Declare boundary conditions.
[5]:
zero = df.Constant(0)
bc_bot_uy = df.DirichletBC(W.sub(1).sub(1), zero, bndry, 1)
bc_bot_vy = df.DirichletBC(W.sub(0).sub(1), zero, bndry, 1)
bcs = [bc_bot_uy, bc_bot_vy]
Write the weak form.
[6]:
rho = df.Constant(1e3)
mu1 = df.Constant(10e3)
mu2 = df.Constant(100)
G = df.Constant(10e3)
dt = 0.05 #time step
t = dt #time
Tappl = df.Expression(("0.0", "(t<5.0) ? ((x[0]>0.2+0.4*t && x[0]<0.7+0.4*t) ? -5.0e3 : 0.0) : 0.0"), t = t, degree = 1)
v_, u_, b11_, b12_, b22_, p_ = df.TestFunctions(W)
w = df.Function(W)
v, u, b11, b12, b22, p = df.split(w)
w0 = df.Function(W)
v0, u0, b110, b120, b220, _ = df.split(w0)
B_ = df.as_tensor([[b11_,b12_],[b12_,b22_]])
B = df.as_tensor([[b11,b12],[b12,b22]])
B0 = df.as_tensor([[b110,b120],[b120,b220]])
I = df.Identity(2)
Fhat = I + df.grad(u)
Jhat = df.det(Fhat)
L = df.grad(v)
invFhat = df.inv(Fhat)
Lhat = L*invFhat
Dhat = 0.5*(Lhat + Lhat.T)
That = -p*I + 2.0*mu2*Dhat + G*(B-I)
aleder = df.dot(invFhat, v - (u-u0)/dt)
matderv = (v-v0)/dt + df.grad(v)*aleder
oldroydderB = (B-B0)/dt + B.dx(0)*aleder[0] + B.dx(1)*aleder[1] - Lhat*B - B*(Lhat.T)
Eq1 = Jhat*df.tr(df.grad(v)*invFhat)*p_*df.dx
Eq2 = Jhat*df.inner(rho*matderv, v_)*df.dx + df.inner(That, df.grad(v_))*df.dx - df.inner(Tappl, v_)*ds(2)
Eq3 = Jhat*df.inner(oldroydderB, B_)*df.dx + Jhat*G/mu1*df.inner(B - I, B_)*df.dx
Eq4 = df.inner(df.grad(u), df.grad(u_))*df.dx
n = df.FacetNormal(mesh)
h = df.CellDiameter(mesh)
Eq5 = -df.inner(df.grad(u)*n, u_)*(ds(1)+ds(2)+ds(3)) + df.inner(df.grad(u_)*n, u - (u0 + dt*v))*(ds(1)+ds(2)+ds(3)) + 1000.0/h*df.inner(u - (u0 + dt*v), u_)*(ds(1)+ds(2)+ds(3))
Eq = Eq1 + Eq2 + Eq3 + Eq4 + Eq5
Prepare the non-linear solver and define the initial conditions.
[7]:
problem=df.NonlinearVariationalProblem(Eq,w,bcs,df.derivative(Eq,w))
solver=df.NonlinearVariationalSolver(problem)
solver.parameters['newton_solver']['report'] = True
solver.parameters['newton_solver']['linear_solver'] = 'mumps'
solver.parameters['newton_solver']['lu_solver']['report'] = False
solver.parameters['newton_solver']['absolute_tolerance'] = 5e-9
solver.parameters['newton_solver']['relative_tolerance'] = 5e-9
ic = df.Expression(("0.0","0.0","0.0","0.0","1.0","0.0","1.0","0.0"), degree = 1)
w.assign(df.interpolate(ic, W))
w0.assign(df.interpolate(ic, W))
Calling FFC just-in-time (JIT) compiler, this may take some time.
Calling FFC just-in-time (JIT) compiler, this may take some time.
We create the XDMF files for storing the results.
[8]:
# Initialize the files for writing the results
files = []
for name in ['v', 'u', 'B11', 'B12', 'B22', 'p']:
with df.XDMFFile(df.MPI.comm_world, f"result/{name}.xdmf") as xdmf:
xdmf.parameters["flush_output"] = True
files.append(xdmf)
Iterate over the time steps until the t_end is not reached.
[ ]:
while t < 8.0:
df.info(f"t = {t:.2f}")
Tappl.t = t
solver.solve()
# write the time-step into the file
for func, name, xdmf in zip(w.split(True), ['v', 'u', 'B11', 'B12', 'B22', 'p'], files):
func.rename(name, name)
xdmf.write(func, t)
w0.assign(w)
t += dt
Exercises¶
Try different material parameters, study the effect of the ratio \(\mu_1/G\).
Program the roller to roll forward and back, and then have a rest.