# Implementation#

Author: Jørgen S. Dokken

In this tutorial, you will learn how to:

Use a vector function space

Create a constant boundary condition on a vector space

Visualize cell wise constant functions

Compute Von Mises stresses

## Test problem#

As a test example, we will model a clamped beam deformed under its own weigth in 3D. This can be modeled, by setting the right-hand side body force per unit volume to \(f=(0,0,-\rho g)\) with \(\rho\) the density of the beam and \(g\) the acceleration of gravity. The beam is box-shaped with length \(L\) and has a square cross section of width \(W\). we set \(u=u_D=(0,0,0)\) at the clamped end, x=0. The rest of the boundary is traction free, that is, we set \(T=0\). We start by defining the physical variables used in the program.

```
# Scaled variable
L = 1
W = 0.2
mu = 1
rho = 1
delta = W/L
gamma = 0.4*delta**2
beta = 1.25
lambda_ = beta
g = gamma
```

We then create the mesh, which will consist of hexahedral elements, along with the function space. We will use the convenience function `VectorFunctionSpace`

. However, we also could have used `ufl`

s functionality, creating a vector element `element = ufl.VectorElement("CG", mesh.ufl_cell(), 1) `

, and intitializing the function space as `V = dolfinx.fem.FunctionSpace(mesh, element)`

.

```
import numpy as np
import ufl
from mpi4py import MPI
from petsc4py.PETSc import ScalarType
from dolfinx import mesh, fem, plot, io
domain = mesh.create_box(MPI.COMM_WORLD, [np.array([0,0,0]), np.array([L, W, W])],
[20,6,6], cell_type=mesh.CellType.hexahedron)
V = fem.VectorFunctionSpace(domain, ("CG", 1))
```

## Boundary conditions#

As we would like to clamp the boundary at \(x=0\), we do this by using a marker function, which locate the facets where \(x\) is close to zero by machine prescision.

```
def clamped_boundary(x):
return np.isclose(x[0], 0)
fdim = domain.topology.dim - 1
boundary_facets = mesh.locate_entities_boundary(domain, fdim, clamped_boundary)
u_D = np.array([0,0,0], dtype=ScalarType)
bc = fem.dirichletbc(u_D, fem.locate_dofs_topological(V, fdim, boundary_facets), V)
```

As we want the traction \(T\) over the remaining boundary to be \(0\), we create a `dolfinx.Constant`

```
T = fem.Constant(domain, ScalarType((0, 0, 0)))
```

We also want to specify the integration measure \(\mathrm{d}s\), which should be the integral over the boundary of our domain. We do this by using `ufl`

, and its built in integration measures

```
ds = ufl.Measure("ds", domain=domain)
```

## Variational formulation#

We are now ready to create our variational formulation in close to mathematical syntax, as for the previous problems.

```
def epsilon(u):
return ufl.sym(ufl.grad(u)) # Equivalent to 0.5*(ufl.nabla_grad(u) + ufl.nabla_grad(u).T)
def sigma(u):
return lambda_ * ufl.nabla_div(u) * ufl.Identity(len(u)) + 2*mu*epsilon(u)
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
f = fem.Constant(domain, ScalarType((0, 0, -rho*g)))
a = ufl.inner(sigma(u), epsilon(v)) * ufl.dx
L = ufl.dot(f, v) * ufl.dx + ufl.dot(T, v) * ds
```

Note

Note that we used `nabla_grad`

and optionally `nabla_div`

for the variational formulation, as oposed to our previous usage of
`div`

and `grad`

. This is because for scalar functions \(\nabla u\) has a clear meaning
\(\nabla u = \left(\frac{\partial u}{\partial x}, \frac{\partial u}{\partial y}, \frac{\partial u}{\partial z} \right)\).

However, if \(u\) is vector valued, the meaning is less clear. Some sources define \(\nabla u\) as a matrix with the elements \(\frac{\partial u_j}{\partial x_i}\), while other sources prefer
\(\frac{\partial u_i}{\partial x_j}\). In DOLFINx `grad(u)`

is defined as the amtrix with element \(\frac{\partial u_i}{\partial x_j}\). However, as it is common in continuum mechanics to use the other definition, `ufl`

supplies us with `nabla_grad`

for this purpose.

## Solve the linear variational problem#

As in the previous demos, we assemble the matrix and right hand side vector and use PETSc to solve our variational problem

```
problem = fem.petsc.LinearProblem(a, L, bcs=[bc], petsc_options={"ksp_type": "preonly", "pc_type": "lu"})
uh = problem.solve()
```

## Visualization#

As in the previous demos, we can either use Pyvista or Paraview for visualization. We start by using Pyvista. Instead of adding scalar values to the grid, we add vectors.

```
import pyvista
pyvista.start_xvfb()
# Create plotter and pyvista grid
p = pyvista.Plotter()
topology, cell_types, geometry = plot.create_vtk_mesh(V)
grid = pyvista.UnstructuredGrid(topology, cell_types, geometry)
# Attach vector values to grid and warp grid by vector
grid["u"] = uh.x.array.reshape((geometry.shape[0], 3))
actor_0 = p.add_mesh(grid, style="wireframe", color="k")
warped = grid.warp_by_vector("u", factor=1.5)
actor_1 = p.add_mesh(warped, show_edges=True)
p.show_axes()
if not pyvista.OFF_SCREEN:
p.show()
else:
figure_as_array = p.screenshot("deflection.png")
```

We could also use Paraview for visualizing this.
As explained in previous sections, we save the solution with `XDMFFile`

.
After opening the file `deformation.xdmf`

in Paraview and pressing `Apply`

, one can press the `Warp by vector button`

or go through the top menu (`Filters->Alphabetical->Warp by Vector`

) and press `Apply`

. We can also change the color of the deformed beam by changing the value in the color menu from `Solid Color`

to `Deformation`

.

```
with io.XDMFFile(domain.comm, "deformation.xdmf", "w") as xdmf:
xdmf.write_mesh(domain)
uh.name = "Deformation"
xdmf.write_function(uh)
```

## Stress computation#

As soon as the displacement is computed, we can compute various stress measures. We will compute the von Mises stress defined as \(\sigma_m=\sqrt{\frac{3}{2}s:s}\) where \(s\) is the deviatoric stress tensor \(s(u)=\sigma(u)-\frac{1}{3}\mathrm{tr}(\sigma(u))I\).

```
s = sigma(uh) -1./3*ufl.tr(sigma(uh))*ufl.Identity(len(uh))
von_Mises = ufl.sqrt(3./2*ufl.inner(s, s))
```

The `von_Mises`

variable is now an expression that must be projected into an appropriate function space so that we can visualize it. As `uh`

is a linear combination of first order piecewise continuous functions, the von Mises stresses will be a cell-wise constant function.

```
V_von_mises = fem.FunctionSpace(domain, ("DG", 0))
stress_expr = fem.Expression(von_Mises, V_von_mises.element.interpolation_points())
stresses = fem.Function(V_von_mises)
stresses.interpolate(stress_expr)
```

In the previous sections, we have only visualized first order Lagrangian functions. However, the Von Mises stresses are piecewise constant on each cell. Therefore, we modify our plotting routine slightly. The first thing we notice is that we now set values for each cell, which has a one to one correspondence with the degrees of freedom in the function space.

```
warped.cell_data["VonMises"] = stresses.vector.array
warped.set_active_scalars("VonMises")
p = pyvista.Plotter()
p.add_mesh(warped)
p.show_axes()
if not pyvista.OFF_SCREEN:
p.show()
else:
stress_figure = p.screenshot(f"stresses.png")
```