# Tutorial 04: Finite Elements for the Wave Equation

In this tutorial we solve the wave equation formulated as a first order in time system. This way the example serves as a model for the treatment of systems of partial differential equations in PDELab. This tutorial depends on tutorial 01 and 03.

# PDE Problem

As an example for a system we consider the wave equation with reflective boundary conditions:

\begin{align} \label{eq:WaveEquation}
\partial_{tt} u-c^2\Delta u  &= 0 &&\text{in $\Omega\times\Sigma$},\\
u &= 0 &&\text{on $\partial\Omega$},\\
u &= q &&\text{at $t=0$},\\
\partial_t u &= w &&\text{at $t=0$},
\end{align}

where $c$ is the speed of sound.
Renaming $u_0=u$ and introducing $u_1=\partial_t u_0 =\partial_t u$ we can write the wave equation as a system of two equations:

\begin{align}
\partial_t u_1 - c^2\Delta u_0 &=0 &&\text{in $\Omega\times\Sigma$}, \label{eq:2a}\\
\partial_t u_0 - u_1 &= 0 &&\text{in $\Omega\times\Sigma$},  \label{eq:2b}\\
u_0 &= 0 &&\text{on $\partial\Omega$},\\
u_1 &= 0 &&\text{on $\partial\Omega$},\\
u_0 &= q &&\text{at $t=0$},\\
u_1 &= w &&\text{at $t=0$}.\label{eq:2f}
\end{align}

Since $u_0=u=0$ on the boundary we also have $\partial_t u = u_1 = 0$ on the boundary.
But one may also omit the boundary condition on $u_1$.

Note that there are several alternative ways how to write the scalar equation
\eqref{eq:WaveEquation} as a system of PDEs:

1. Eriksson et al. [[6]](#cit1) apply the Laplacian to equation \eqref{eq:2b}
\begin{equation}  \label{eq:Eriksson}
\Delta \partial_t u_0 - \Delta u_1 = 0
\end{equation}
which has advantages for energy conservation but requires additional smoothness properties.
2. Alternatively, we may introduce the abbreviations $q=\partial_t u$ and $w=-\nabla u$, so $\partial_{tt} u - c^2 \Delta u =\partial_{tt} u - c^2 \nabla\cdot\nabla u = \partial_{t} q + c^2 \nabla\cdot w = 0$. Taking partial derivatives of the introduced variables we obtain $\partial_{x_i} q=\partial_{x_i} \partial_t u = \partial_t \partial_{x_i}  u = - \partial_t w_i$. This results in a first-order hyperbolic system of PDEs for $q$ and $w$
\begin{align*}
\partial_t q + c^2 \nabla\cdot w &= 0\\
\partial_t w + \nabla q &= 0
\end{align*}
which are called equations of linear acoustics. This formulation is physically more relevant. It can be modified to handle discontinuous material properties and upwind finite volume methods can be used for numerical treatment.

Here we will stay, however, with the simplest formulation \eqref{eq:2a} - \eqref{eq:2f} for simplicity.

## Weak Formulation

Multiplying \eqref{eq:2a}
with the test function $v_0$ and \eqref{eq:2b} with the test function $v_1$
and using integration by parts we arrive at the weak formulation: Find $(u_0(t),u_1(t))\in
U_0\times U_1$ s.t.
\begin{align}
  d_t (u_1,v_0)_{0,\Omega} + c^2 (\nabla u_0, \nabla v_0)_{0,\Omega} &=
  0 \quad \forall v_0 \in U_0 \notag \\
  d_t (u_0,v_1)_{0,\Omega} - (u_1,v_1)_{0,\Omega} &= 0 \quad \forall
  v_1 \in U_1 \label{eq:WeakFormSystem}
\end{align}
where we used the notation of the $L^2$ inner product $(u,v)_{0,\Omega} = \int_\Omega
u v \, dx$. An equivalent formulation to (\ref{eq:WeakFormSystem}) that hides the system structure reads as follows:
\begin{equation}
\label{eq:WeakForm}
\begin{split}
d_t &\left[ (u_0,v_1)_{0,\Omega} + (u_1,v_0)_{0,\Omega}\right] \\
&\hspace{20mm}+ \left[ c^2 (\nabla u_0,\nabla v_0)_{0,\Omega} -(u_1,v_1)_{0,\Omega} \right] = 0
\quad \forall (v_0,v_1)\in U_0\times U_1
\end{split}
\end{equation}
With the latter we readily identify the temporal and spatial residual forms:
\begin{align}
m^{\text{WAVE}}((u_0,u_1),(v_0,v_1)) &= (u_0,v_1)_{0,\Omega} + (u_1,v_0)_{0,\Omega},
\label{eq:TemporalResForm}\\
r^{\text{WAVE}}((u_0,u_1),(v_0,v_1)) &= c^2 (\nabla u_0,\nabla
v_0)_{0,\Omega} - (u_1,v_1)_{0,\Omega} \; , \label{eq:SpatialResForm}
\end{align}
while with the former the system structure is more visible which might help to understand the implementation presented in section
[Realization in PDELab](#implementation).
The spaces $U_0$ and $U_1$ can differ as different types of boundary conditions can be incorporated into the ansatz spaces. But here both spaces are constrained by homogeneous Dirichlet boundary conditions.

## Generalization

The abstract setting of PDELab with its weighted residual formulation carries over to the case of systems of
partial differential equations when cartesian products of functions spaces are introduced, i.e. the abstract *stationary* problem then reads
\begin{equation}
\text{Find $u_h\in U_h=U_h^1\times \ldots \times U_h^s$ s.t.:} \quad r_h(u_h,v)=0
\quad \forall v\in V_h=V_h^1\times\ldots\times V_h^s
\label{Eq:BasicSystemBuildingBlock}
\end{equation}
with $s$ the number of components in the system. Again the concepts are completely orthogonal meaning that $r_h$ might be affine linear or nonlinear in its first argument and the instationary case works as well.

From an organizational point of view it makes sense to allow that a component space $U_h^i$ in the cartesian product is itself a product space. This naturally leads to a *tree structure* in the
function spaces.

Consider as an example the Stokes equation in $d$ space dimensions.There one has pressure $p$ and velocity $v$ with components $v_1,\ldots,v_d$ as unknowns. An appropriate function space then would be $$ U = (P,(V^1,\ldots,V^d)).$$

# Finite Element Method

The finite element method applied to \eqref{eq:WeakForm} is straightforward. We may use the conforming space $V_h^{k,d}(\mathcal{T}_h)$ of degree $k$ in dimension $d$ for each of the components. Typically one would choose
the same polynomial degree for both components.

# Realization in PDELab
<a id= "implementation"> </a>

```c++
#include<dune/jupyter.hh>
#include "wavefem.hh"
```

```c++
// open ini file
Dune::ParameterTree ptree;
Dune::ParameterTreeParser ptreeparser;
ptreeparser.readINITree("exercise04.ini",ptree);

// read ini file
const int refinement = ptree.get<int>("grid.refinement");
```

Choose polynomial degree:

```c++
const int degree = 2;
```

Instantiation of a 1D grid, which allows for a faster execution time compared to the 2D grid.

```c++
static const int dim = 1;
// read grid parameters from input file
using DF = Dune::OneDGrid::ctype;
auto a = ptree.get<DF>("grid.oned.a");
auto b = ptree.get<DF>("grid.oned.b");
auto N = ptree.get<unsigned int>("grid.oned.elements");

// create equidistant intervals
using Intervals = std::vector<DF>;
Intervals intervals(N+1);
for(unsigned int i=0; i<N+1; ++i)
intervals[i] = a + DF(i)*(b-a)/DF(N);

// Construct grid
using Grid = Dune::OneDGrid;
Grid grid(intervals);
grid.globalRefine(refinement);

// call generic function
using GV = Dune::OneDGrid::LeafGridView;
GV gv = grid.leafGridView();
```

Instantiate a 2D UGGrid, as used in previous tutorials.

```c++
/*
static const int dim = 2;
using Grid = Dune::UGGrid<dim>;
using DF = Grid::ctype;
Dune::FieldVector<DF,dim> L;
//upper right
L[0] = ptree.get("grid.structured.LX",(double)1.0);
L[1] = ptree.get("grid.structured.LY",(double)1.0);
std::array<unsigned int,dim> N;
N[0] = ptree.get("grid.structured.NX",(unsigned int)10);
N[1] = ptree.get("grid.structured.NY",(unsigned int)10);
//lower left
Dune::FieldVector<double,dim> lowerleft(0.0);

// build a structured simplex grid
auto gridp = Dune::StructuredGridFactory<Grid>::createSimplexGrid(lowerleft, L, N);

gridp->globalRefine(refinement);
using GV = Grid::LeafGridView;
GV gv=gridp->leafGridView();
*/
```

```c++
using FEM = Dune::PDELab::PkLocalFiniteElementMap<GV,DF,double,degree>;
FEM fem(gv);
```

There are several changes now in the driver due to the system of PDEs.The first step is to set up the grid function space using the given finite element map:

```c++
using RF = double;

// Make grid function space used per component
using CON = Dune::PDELab::ConformingDirichletConstraints;
using VBE0 = Dune::PDELab::ISTL::VectorBackend<>;
using GFS0 = Dune::PDELab::GridFunctionSpace<GV,FEM,CON,VBE0>;
GFS0 gfs0(gv,fem);
```

```c++
// Make grid function space for the system
using VBE =
  Dune::PDELab::ISTL::VectorBackend<
    Dune::PDELab::ISTL::Blocking::fixed
  >;
using OrderingTag = Dune::PDELab::EntityBlockedOrderingTag;
using GFS =
  Dune::PDELab::PowerGridFunctionSpace<GFS0,2,VBE,OrderingTag>;
GFS gfs(gfs0);
```

- *solution to exercise 3:*

```c++
/*
using VBE = Dune::PDELab::ISTL::VectorBackend <
              Dune::PDELab::ISTL::Blocking::none
            >;
*/
```

The next step is to set up the product space containing two components. This is done by the following code section:

PDELab offers two different class templates to build product spaces. The one used here is `PowerGridFunctionSpace` which creates a product of a compile-time given number (2 here)
of *identical* function spaces (`GFS0` here) which may only differ in the constraints. With the class template`CompositeGridFunctionSpace` you can create a product space where all components might be different spaces.
We also have to set up names for the child spaces to facilitate VTK output later on:

```c++
using namespace Dune::TypeTree::Indices;
gfs.child(_0).name("u0");
gfs.child(_1).name("u1");
```

An important aspect of product spaces is the ordering of the corresponding degrees of freedom. Often the solvers need to exploit an underlying block structure of the matrices.

This works in two stages: An ordering has first to be specified when creating product spaces
which is then subsequently exploited in the backend. Here we use the `EntityBlockedOrderingTag` to specify that all degrees of freedom related to a geometric entity should be numbered consecutively in the coefficient vector. Other options are the `LexicographicOrderingTag` ordering first all degrees of freedom of the first component space, then all of the second component space and so on. With the Iterative Solver Template Library ISTL it is now possible to exploit the block structure at compile-time.
Here we use the tag `fixed` in the ISTL vector backend to indicate that at this level we want to create blocks of fixed size (in this case the block size will be two --
corresponding to the degrees of freedom per entity). Another option would be the tag `none` which is the default. Then the degrees of freedom are still ordered in the specified way but no block structure is introduced on the ISTL level. *Important notice:* Using fixed block structure in ISTL requires that there is the same number of degrees of freedom per entity. This is true for polynomial degrees one and two but *not* for higher polynomial degree!

In order to define a function that specifies the initial value we can use the same techniques as in the scalar case. A PDELab grid function
is be constructed from the lambda closure

```c++
auto u = Dune::PDELab::makeGridFunctionFromCallable(
    gv,
    [](const auto& x){
      Dune::FieldVector<RF,dim> rv(0.0);
      for (int i=0; i<dim; i++) rv[0] += (x[i]-0.375)*(x[i]-0.375);
      rv[0] = std::max(0.0,1.0-8.0*sqrt(rv[0]));
      return rv;
   }
);;
```

The lambda closure is now returning two components in a `FieldVector`. The first component is the initial value for $u$ and the second component
is the initial value for $\partial_t u$.

Using the grid function a coefficient vector can now be initialized:

```c++
using Z = Dune::PDELab::Backend::Vector<GFS,RF>;
Z z(gfs); // initial value
Dune::PDELab::interpolate(u,gfs,z);
```

The next step is to assemble the constraints container for the composite function space. Unfortunately there is currently no way to define the constraints for both components in one go. We need to set up a separate lambda closure for each component:

```c++
auto b0 = Dune::PDELab::
  makeBoundaryConditionFromCallable(
    gv,
    [](const auto& x){return true;}
  );;
```

```c++
auto b1 = Dune::PDELab::
  makeBoundaryConditionFromCallable(
    gv,
    [](const auto& x){return true;}
  );;
```

and then combine it using:

```c++
using B = Dune::PDELab::CompositeConstraintsParameters<
  decltype(b0),decltype(b1)
  >;
B b(b0,b1);
```

Note that you could define different constraints for each component space although it is the same underlying function space.
Now the constraints container can be assembled as before:

```c++
using CC = typename GFS::template ConstraintsContainer<RF>::Type;
CC cc;
Dune::PDELab::constraints(b,gfs,cc);
```

```c++
std::cout << "constrained dofs=" << cc.size() << " of "
          << gfs.globalSize() << std::endl;
set_constrained_dofs(cc,0.0,z); // set zero Dirichlet boundary conditions
```

Then the VTK writer is prepared and the first file is written.

```c++
int subsampling=ptree.get("output.subsampling",(int)1);
using VTKWRITER = Dune::SubsamplingVTKWriter<GV>;
VTKWRITER vtkwriter(gv,Dune::refinementIntervals(subsampling));
std::string filename=ptree.get("output.filename","output") + std::to_string(dim) + "d";

std::filesystem::create_directory(filename);

//write filename to .txt file
std::ofstream out("name.txt");
out << filename;
out.close();

using VTKSEQUENCEWRITER = Dune::VTKSequenceWriter<GV>;
VTKSEQUENCEWRITER vtkSequenceWriter(
  std::make_shared<VTKWRITER>(vtkwriter),filename,filename,"");
```

As we do not want to manually extract the subspaces for $u_0$ and $u_1$ from the overall space to add to them to the VTK writer, we call a PDELab helper function that handles this automatically:

```c++
// add data field for all components of the space to the VTK writer
Dune::PDELab::addSolutionToVTKWriter(vtkSequenceWriter,gfs,z);
```

The rest of the is the same as for tutorial 03 except that a linear solver is used instead of Newton's method.

```c++
vtkSequenceWriter.write(0.0,Dune::VTK::appendedraw);

// Make instationary grid operator
double speedofsound=ptree.get("problem.speedofsound",(double)1.0);
using LOP = WaveFEM<FEM>;
// using LOP = WaveFEMElip<FEM>;
LOP lop(speedofsound);
using TLOP = WaveL2<FEM>;
// using TLOP = WaveElip<FEM>;
TLOP tlop;
using MBE = Dune::PDELab::ISTL::BCRSMatrixBackend<>;
int degree = ptree.get("fem.degree",(int)1);
MBE mbe((int)pow(1+2*degree,dim));
using GO0 = Dune::PDELab::GridOperator<GFS,GFS,LOP,MBE,RF,RF,RF,CC,CC>;
GO0 go0(gfs,cc,gfs,cc,lop,mbe);
using GO1 = Dune::PDELab::GridOperator<GFS,GFS,TLOP,MBE,RF,RF,RF,CC,CC>;
GO1 go1(gfs,cc,gfs,cc,tlop,mbe);
using IGO = Dune::PDELab::OneStepGridOperator<GO0,GO1>;
IGO igo(go0,go1);

// Linear problem solver
//--iterative solver
using LS = Dune::PDELab::ISTLBackend_SEQ_BCGS_SSOR;
LS ls(5000,false);
using SLP = Dune::PDELab::StationaryLinearProblemSolver<IGO,LS,Z>;
SLP slp(igo,ls,1e-8);
//--direct solver
//using LDS = Dune::PDELab::ISTLBackend_SEQ_UMFPack;
//LDS lds;
//using SLP = Dune::PDELab::StationaryLinearProblemSolver<IGO,LDS,Z>;
//SLP slp(igo,lds,1e-8);
//---

// select and prepare time-stepping scheme
int torder = ptree.get("fem.torder",(int)1);
Dune::PDELab::OneStepThetaParameter<RF> method1(1.0);
Dune::PDELab::Alexander2Parameter<RF> method2;
Dune::PDELab::Alexander3Parameter<RF> method3;
Dune::PDELab::TimeSteppingParameterInterface<RF>* pmethod=&method1;
if (torder==1) pmethod = &method1;
if (torder==2) pmethod = &method2;
if (torder==3) pmethod = &method3;
if (torder<1||torder>3) std::cout<<"torder should be in [1,3]"<<std::endl;
Dune::PDELab::OneStepMethod<RF,IGO,SLP,Z,Z> osm(*pmethod,igo,slp);
osm.setVerbosityLevel(2);

// subspaces
using U0SUB = Dune::PDELab::GridFunctionSubSpace<GFS,Dune::TypeTree::TreePath<0> >;
U0SUB u0sub(gfs);
using U1SUB = Dune::PDELab::GridFunctionSubSpace<GFS,Dune::TypeTree::TreePath<1> >;
U1SUB u1sub(gfs);

// Make discrete grid functions for components
using U0DGF = Dune::PDELab::DiscreteGridFunction<U0SUB,Z>;
U0DGF u0dgf(u0sub,z);
using U1DGF = Dune::PDELab::DiscreteGridFunction<U1SUB,Z>;
U1DGF u1dgf(u1sub,z);

// initialize simulation time
RF time = 0.0;
```

```c++
// time loop
RF T = ptree.get("problem.T",(RF)1.0);
RF dt = ptree.get("fem.dt",(RF)0.1);
while (time<T-1e-8)
{
  // do time step
  Z znew(z);
  osm.apply(time,dt,z,znew);

  // accept time step
  z = znew;
  time+=dt;

  //exercise 5: put your code here

  // output to VTK file
  vtkSequenceWriter.write(time,Dune::VTK::appendedraw);
}
```

```c++
vtkSequenceWriter
```

# Local Operator

## Spatial Local Operator

The spatial residual form \eqref{eq:SpatialResForm} is implemented by the local operator `WaveFEM` in file `wavefem.hh`. Cache construction and flags settings are the same as in tutorial 01 and 03. Only volume terms are used here. Note also that no parameter object is necessary as the only parameter is the speed of sound $c$.

### `alpha_volume` Method

The method `alpha_volume` has the *same* interface as in the scalar case:

```c++
template<typename EG, typename LFSU, typename X, typename LFSV, typename R>
  void alpha_volume (const EG& eg, const LFSU& lfsu, const X& x, const LFSV& lfsv, R& r) const
```
However the trial and test function spaces `LFSU` and `LFSV` now reflect the component structure of the global function space, i.e. they consist of two components. *Important notice: Here we assume that trial and test space are identical (up to constraints) and that also both components are identical!*
The two components can be extracted with the following code
```c++
// select the two components (but assume Galerkin scheme U=V)
using namespace Dune::TypeTree::Indices;
auto lfsu0 = lfsu.child(_0);
auto lfsu1 = lfsu.child(_1);
```
The function spaces `lfsu0` and `lfsu1` are now scalar spaces (which we assume to be identical).

After extracting the dimension
```c++
const int dim = EG::Entity::dimension;
```
we select a quadrature rule
```c++
auto geo = eg.geometry();
const int order = 2*lfsu0.finiteElement().localBasis().order();
auto rule = Dune::PDELab::quadratureRule(geo,order);
```
and may now loop over the quadrature points.
For each quadrature point, evaluate the basis function of the first component:
```c++
for (const auto& ip : rule)
  {
    auto& phihat =
    cache.evaluateFunction(ip.position(),lfsu0.finiteElement().localBasis());
```
As the components are identical we need only evaluate the basis once and can compute the value of $u_1$ at the quadrature point
```c++
    // evaluate u1
    RF u1=0.0;
    for (std::size_t i=0; i<lfsu0.size(); i++) u1 += x(lfsu1,i)*phihat[i];
```
Then we evaluate the gradients of the basis functions
```c++
    auto& gradphihat =
    cache.evaluateJacobian(ip.position(),lfsu0.finiteElement().localBasis());
```
transform them from the reference element to the real element
```c++
    const auto S = geo.jacobianInverseTransposed(ip.position());
    auto gradphi = makeJacobianContainer(lfsu0);
    for (std::size_t i=0; i<lfsu0.size(); i++)
       S.mv(gradphihat[i][0],gradphi[i][0]);
```
and compute the gradient of $u_0$:
```c++
    Dune::FieldVector<RF,dim> gradu0(0.0);
    for (std::size_t i=0; i<lfsu0.size(); i++)
      gradu0.axpy(x(lfsu0,i),gradphi[i][0]);
```
With the integration factor
```c++
    RF factor = ip.weight() * geo.integrationElement(ip.position());
 ```
the residuals can now be accumulated:
```c++
   for (std::size_t i=0; i<lfsu0.size(); i++) {
     r.accumulate(lfsu0,i,c*c*(gradu0*gradphi[i][0])*factor);
     r.accumulate(lfsu1,i,-u1*phihat[i]*factor);
```

### `jacobian_volume` Method

As the problem is linear it is advisable to also implement the `jacobian_volume` method for efficiency and accuracy.
The interface is the same as in the scalar case:
```c++
template<typename EG, typename LFSU, typename X, typename LFSV, typename M>
void jacobian_volume (const EG& eg, const LFSU& lfsu, const X& x, const LFSV& lfsv,
                      M& mat) const
```
Component selection, quadrature rule selection and basis evaluation are the same as in `alpha_volume`.
We only consider the accumulation of the Jacobian entries here:
```c++
// integrate both equations
RF factor = ip.weight() * geo.integrationElement(ip.position());
for (std::size_t j=0; j<lfsu0.size(); j++)
  for (std::size_t i=0; i<lfsu0.size(); i++) {
    mat.accumulate(lfsu0,i,lfsu0,j,c*c*(gradphi[j][0]*gradphi[i][0])*factor);
    mat.accumulate(lfsu1,i,lfsu1,j,-phihat[j]*phihat[i]*factor);
  }
```
Note how the diagonal sub-blocks of the Jacobian with respect to the first and second component are accessed. Finally, `WaveFEM` also implements the matrix-free versions
for Jacobian application.

## Temporal Local Operator

The temporal residual form \eqref{eq:TemporalResForm} is implemented by the local operator `WaveL2` in file `wavefem.hh`. Cache construction and flags settings are the same as in tutorial 01 and 03. Only volume terms are used here.

### `alpha_volume` Method

The `alpha_volume` method is pretty similar
to the one in the spatial operator, except that the value of $u_0$ is needed instead of the gradient. Here we just show the residual accumulation:
```c++
 // integration factor
RF factor = ip.weight() * geo.integrationElement(ip.position());

// integrate u*phi_i
for (std::size_t i=0; i<lfsu0.size(); i++) {
  r.accumulate(lfsu0,i,u1*phihat[i]*factor);
  r.accumulate(lfsu1,i,u0*phihat[i]*factor);
}
```
Note that $u_1$ is integrated with respect to test function $v_0$
and vice versa.

### `jacobian_volume` Method

The corresponding Jacobian entries are accumulated in the `jacobian_volume` method:
```c++
// integration factor
RF factor = ip.weight() * geo.integrationElement(ip.position());

// loop over all components
for (std::size_t j=0; j<lfsu0.size(); j++)
  for (std::size_t i=0; i<lfsu0.size(); i++) {
    mat.accumulate(lfsu0,i,lfsu1,j,phihat[j]*phihat[i]*factor);
    mat.accumulate(lfsu1,i,lfsu0,j,phihat[j]*phihat[i]*factor);
  }
```
That's it! 293 lines of code to implement the finite element method for the wave equation.

# Exercise

## Getting to know the Code

The code of this exercise solves the wave equation formulated as a first order in time system. As already explained above, we can write the wave equation as a system of two equations by substituting $u_0=u$ and introducing $u_1=\partial_t u_0 =\partial_t u$.

As in the previous exercises you can control most of the settings through the ini-file `exercise04.ini`. Get an overview of the configurable settings, compile and run `exercise04`.

The program writes output with the extension `pvd`. This is one of several ways to write VTK output for the instationary case, c.f. the documentation of tutorial03. The `pvd`-file can be visualized by ParaView and consists of a collection of the corresponding `vtu`-files. In order to visualize a 1D solution, one can apply the "Plot Over Line" filter. Note that our solution is always given by $u_0$.

- *1d solution of wave equation*

<video src="wave1d.ogv" controls width="60%">

- *2d solution of wave equation*

<video src="wave2d.ogv" controls width="100%">

## Try various time integrators, in particular the Crank-Nicolson method

We want to examine the numerical solution under different time discretization schemes, eg. Implicit Euler or Crank-Nicolson. In order to change the time discretization scheme you will have to search for the line
```c++
Dune::PDELab::Alexander2Parameter<RF> pmethod;
```

Recall the previous `exercise03` and change the one step $\theta$ scheme in a way that corresponds to the Crank-Nicolson method.

 **Note that** you can compare the solutions in ParaView. To do that you have torename the second solution in `exercise04.ini` and use the "Append Attributes" filter before "Plot Over Line". Do not forget to change the parameter `torder` in `exercise04.ini` in order to get the correct scheme.

 **Remark** You can decide which dimension to investigate. A 2D simulation will give you nicer pictures at the cost of longer run times.

- *Crank-Nicolson method:*
```c++
 Dune::PDELab::OneStepThetaParameter<RF> method1(0.5);
```

<video src="wave1dCNIE.ogv" controls width="60%">

## Explore polynomial degrees greater than $2$ by changing the blocking to`none`.

**Step 1:** Find the place where your Local Finite Element Maps are created and make it possible to use polynomial degree 3

```c++
Dune::PDELab::PkLocalFiniteElementMap<GV,DF,double,deg> FEM;
```

Compile your program and check the results.

**Note that** `deg` is a static template parameter.

 - *Program should compile and give a segmentation error when started.*
 - *For the notebook: The initialization of the coefficient vector fails.*

**Step 2:**

Before we start read the part that describes the specification of an ordering when creating product spaces ([here](#ordering)). Using fixed block structure in ISTL requires that the number of degrees of freedom per entity is constant for each geometry type. This is true for polynomial degrees one and two but not for higher polynomial degree!

To avoid segfaults you need to change
```c++
Dune::PDELab::istl::VectorBackend<Dune::PDELab::istl::Blocking::fixed> VBE;
```
  to
```c++
Dune::PDELab::istl::VectorBackend<Dune::PDELab::istl::Blocking::none> VBE;
```
within the notebook.

## Changing the Local Operator

 Now consider the elliptic projection as in Eriksson et al. \cite{Eriksson}, namely applying the Laplacian to equation \eqref{eq:2b}
  \begin{equation}\label{eq:elip}
    -\Delta \partial_t u_0 + \Delta u_1 = 0,
  \end{equation}
which has the advantage of energy conservation but requires additional smoothness properties.

The main work is now to change the local operators given in the file `wavefem.hh`. This is done in several steps:

1. Copy the file `wavefem.hh` to a new file and rename it.
2. Rename the local operators in the new file, e.g. change`WaveL2` to `WaveElip`.
3. Include your new file in `exercise04.cc` and change the types of `LOP` and `TLOP` in
   the notebook.
```c++
// Make instationary grid operator
double speedofsound = ptree.get("problem.speedofsound",1.0);
typedef WaveFEM<FEM> LOP;
LOP lop(speedofsound);
typedef WaveL2<FEM> TLOP;
TLOP tlop;
```
After these preparations you need to implement the following change:
$$ \partial_t u_0 - u_1 = 0 \Rightarrow \Delta \partial_t u_0 -
  \Delta u_1 = 0.$$

1. In the spatial local operator, change:
    $$ - u_1 = 0 \Rightarrow -  \Delta u_1 = 0$$

    See how it is done for the $\Delta u_0$, we recall the
    corresponding part of `WaveFEM::alpha_volume()`:
```c++
// integrate both equations
RF factor = ip.weight() * geo.integrationElement(ip.position());
for (size_t i=0; i<lfsu0.size(); i++) {
	r.accumulate(lfsu0,i,c*c*(gradu0*gradphi[i][0])*factor);
	r.accumulate(lfsu1,i,-u1*phihat[i]*factor);
}
```

- *solution for `WaveFEMElip::alpha_volume`*

```c++
...
// compute gradient of u1
Dune::FieldVector<RF,dim> gradu1(0.0);
for (size_t i=0; i<lfsu1.size(); i++)
  gradu1.axpy(x(lfsu1,i),gradphi[i][0]);

// integrate both equations
RF factor = ip.weight() * geo.integrationElement(ip.position());
for (size_t i=0; i<lfsu0.size(); i++) {
  r.accumulate(lfsu0,i,c*c*(gradu0*gradphi[i][0])*factor);
  r.accumulate(lfsu1,i,-(gradu1*gradphi[i][0])*factor);
```

2. In the temporal local operator, change:
    $$ \partial_t u_0  = 0 \Rightarrow \Delta \partial_t u_0= 0$$

    See how it is done for the $\Delta u_0$, we recall the
    corresponding part of `WaveL2::alpha_volume()`:
```c++
// integrate u*phi_i
for (size_t i=0; i<lfsu0.size(); i++) {
	r.accumulate(lfsu0,i,u1*phihat[i]*factor);
	r.accumulate(lfsu1,i,u0*phihat[i]*factor);
}
```

 - *solution for `WaveElip::alpha_volume`*

```c++
// integrate u*phi_i
for (size_t i=0; i<lfsu0.size(); i++) {
  r.accumulate(lfsu0,i,u1*phihat[i]*factor);
  r.accumulate(lfsu1,i,(gradu0*gradphi[i][0])*factor);
```

3. Do not forget to update the `jacobian_volume()` methods accordingly. As the problem is linear, this should not be too difficult.

- *solution for `WaveFEMElip::jacobian_volume`*

```c++
for (size_t i=0; i<lfsu0.size(); i++) {
  mat.accumulate(lfsu0,i,lfsu0,j,c*c*(gradphi[j][0]*gradphi[i]   [0])*factor);
  mat.accumulate(lfsu1,i,lfsu1,j,-(gradphi[j][0]*gradphi[i][0])*factor);
}
 ```

- *solution for `WaveElip::jacobian_volume`*

 ```c++
for (size_t i=0; i<lfsu0.size(); i++)
  S.mv(gradphihat[i][0],gradphi[i][0]);
// loop over all components
for (size_t j=0; j<lfsu0.size(); j++)
  for (size_t i=0; i<lfsu0.size(); i++) {
    mat.accumulate(lfsu0,i,lfsu1,j,phihat[j]*phihat[i]*factor);
    mat.accumulate(lfsu1,i,lfsu0,j,(gradphi[j][0]*gradphi[i][0])*factor);
    }
```

## Energy Conservation

 If we multiply \eqref{eq:2a} by $u_1$ and \eqref{eq:elip} by $u_0$ and add, the terms $-(\Delta u_0,u_1 )$ and $(\Delta u_1,u_0 )$ cancel out, leading to the conclusion that the energy $$E(t) = \|u_1\|^2 + \| \nabla u_0\|^2 $$ is constant in time.

Your task is to check the energy conservation for the elliptic projection and Crank-Nicolson in time. You can use the following PDELab utilities:
```c++
Dune::PDELab::SqrGridFunctionAdapter
Dune::PDELab::integrateGridFunction
Dune::PDELab::DiscreteGridFunctionGradient
Dune::PDELab::SqrGridFunctionAdapter
```

If you have problems with this task check the online documentation https://www.dune-project.org/doxygen/pdelab/master/ or the solution.

 - *solution for energy conservation*

```c++
// Energy - integrate u square
typedef Dune::PDELab::SqrGridFunctionAdapter<U1DGF> SQRU1DGF;
SQRU1DGF u1sqrdgf(u1dgf);

typename SQRU1DGF::Traits::RangeType sqru1(0.0);
Dune::PDELab::integrateGridFunction(u1sqrdgf, sqru1, 2*degree);

using DGFU0G = Dune::PDELab::DiscreteGridFunctionGradient<U0SUB, Z>;
DGFU0G dgfu0g(u0sub,z);
//gradient square
using SQRDGFU0G = Dune::PDELab::SqrGridFunctionAdapter<DGFU0G>;
SQRDGFU0G sqrdgfu0g(dgfu0g);
//integrate gradient square
typename SQRDGFU0G::Traits::RangeType sqru0g(0.0);
Dune::PDELab::integrateGridFunction(sqrdgfu0g, sqru0g, 2*degree);

std::cout << "::: energy          " << sqru1 + sqru0g << std::endl;
```

## Additional Task

If you are done with these exercises, you can play with the initial conditions. Use the following setting in one space dimension:

 - Change the initial conditions to $\sin(2x)$ (implement it as a lambda function)
 - Change the domain size to $[0,\pi]$

# Bibliographie

[1] Bastian,  P. *Lecture Notes on Scientific Computing with Partial Differential Equations*. Heidelberg University, 2014. http://conan.iwr.uni-heidelberg.de/teaching/numerik2_ss2014/num2.pdf.

[2] Braess, D. *Finite Elemente*. Springer, 3. edition, 2003.

[3] Brenner, S. C. and Scott, L. R. *The mathematical theory of finite element methods*, Springer, 1994.

[4] Ciarlet, P. G. . *The finite element method for elliptic problems*. SIAM, Classics in Applied Mathematics, 2002.

[5] Elman, H., Silvester, D. and Wathen,  A. *Finite Elements and Fast Iterative Solvers*. Oxford University Press,2005.

[6] Eriksson, K., Estep, D., Hansbo, P. and Johnson, C. *Computational Differential Equations*. Cambridge University Press, 1996. <a id ="cit1"> </a>

[7] Großmann, C. and Roos, H.-G.*Numerische Behandlung partieller Differentialgleichungen*. Teubner, 2006.

[8] Hackbusch, W. *Theorie und Numerik elliptischer Differentialgleichungen*. Teubner, 1986. http://www.mis.mpg.de/preprints/ln/lecturenote-2805.pdf.

[9] Rannacher, R. *Einführung in die Numerische Mathematik II (Numerik partieller Differentialgleichungen)*. Heidelberg University, 2006. http://numerik.iwr.uni-heidelberg.de/~lehre/notes.


# Introduction

Prostate cancer (PCa) is the second most frequent malignancy (after lung cancer) in men worldwide, counting $1,276,106$ new cases and causing $358,989$ deaths ($3.8\%$ of all deaths caused by cancer in men) in $2018$ [1]. Early detection of prostate cancer allows for appropriate management of the disease, and prognostic biomarkers can help clinicians make an appropriate therapeutic decision for each patient and avoid unnecessary treatment [2].

Due to recent progress in imaging, and particularly in MRI, the so-called multi-parametric MRI (mpMRI) that combines T2-weighted imaging (T2W) with functional pulse sequences such as diffusion-weighted imaging (DWI) or dynamic contrast-enhanced (DCE) imaging has shown excellent results in PCa detection and has become the standard of care to achieve accurate and reproducible diagnosis of PCa [3,4].

Pharmacokinetic modeling of the DCE-MRI signal is used to derive estimates of factors related to blood volume and permeability that are hallmarks of the angiogenic phenotype associated with most cancers. The accuracy of DCE relies on the ability to model the pharmacokinetics of an injected tracer, or contrast agent, using the signal intensity changes on sequential magnetic resonance images.

The first pharmacokinetic model was proposed by Kety [5], who described flow-limited tracer uptake in tissue. This was followed by several pharmacokinetic models proposed by Tofts et al [6], Brix et al [7], and Larsson et al [8].

The majority of these models are based on the characterization of the contrast exchange rate between the plasma and the extracellular space through parameters such as $K^{trans}$, that represents the rate at which the contrast agent transfers from the blood to the interstitial space (indicating the tumor microcirculation), the reflux constant, $K_{ep}$, that reflects the rate at which the contrast agent transfers from the extravascular extracellular space back to the blood and the extravascular extracellular leakage volume fraction $v_{e}$, which predominantly reflects the percentage of contrast agent in the extravascular extracellular space.

The study of these parameters helps characterize prostate cancer, so estimating them accurately and robustly is a fundamental step. These parameters are calculated using the Tofts model [9], which is equivalent to the generalized kinetic model [10],

\begin{equation}
   \hspace{7cm} \frac{dC_t}{dt} = K^{trans}C_p - k_{ep}C_t     \hspace{7cm}  (1)
\end{equation}

where interesting parameters are $K^{trans}$, which is the transfer coefficient between blood plasma and the compartment, and the extracellular extravascular fractional volume (EES) ($v_e$). Also $k_{ep}$ is defined as $k_{ep} = K^{trans}/v_e$, and $C_t$ is the concentration of lesson tissue defined as $C_t = C_1 v_e$, where $C_1$ is the leakage space concentration.

To solve equation $(1)$, we use the same approach as [11]. There, the model is restructured and expressed as a convolution as following,

\begin{equation}
  \hspace{4cm}  f(t) = K^{trans} \left( a(t) \otimes \frac{e^{-t/T}}{T} \right) \equiv K^{trans} \frac{1}{T}\int_0^t d\tau\, a(\tau) e^{-(t-\tau)/T}  \hspace{3cm}  (2)
\end{equation}

where $T = 1/k_{ep}$ and $a(t)$ function is an experimental measure, so is only available at discrete times. The previous model is evaluated for values of $T \neq 0$ by interpolating linearly between the measured values of $a(t)$. Instead, for $T = 0$ the result is $f(t) = a(t)$.

Our case study consists on a prostate image with $256\times256\times56$ voxels and $30$ time points for each one. We have fitted this image using the equation $(2)$ implemented at the provided code in the APRICOT repository, which uses the ROOT libraries from CERN [2]. This analysis has been performed using a MPI cluster with $3$ working nodes, reducing the total computation time almost a factor $3$ compared with the same experimentation performed on a single node. The following sections will show how to reproduce this experiment.

## References

[1] P. Rawla. Epidemiology of prostate cancer. World Journal of Oncology, 10(2), 2019.
[2] K. M. V. T. R. P. K.-L. P. Wu X, Reinikainen P. Dynamic contrast-enhanced imaging as a prognostic tool in early diagnosis of prostate cancer: Correlation with psa and clinical stage. Contrast Media Mol Imaging, 2018.
[3] F. Bratan, E. Niaf, C. Melodelima, A. L. Chesnais, R. Souchon, F. Mège-Lechevallier, M. Colombel, and O. Rouvière. Influence of imaging and histological factors on prostate cancer detection and localisation on multi-parametric mri: a prospective study. European Radiology, 23(7):2019–2029, Jul 2013.
[4] J. D. Le, N. Tan, E. Shkolyar, D. Y. Lu, L. Kwan, L. S. Marks, J. Huang, D. J. Margolis, S. S. Raman, and R. E. Reiter. Multifocality and prostate cancer detection by multiparametric magnetic resonance imaging: Correlation with whole-mount histopathology. European Urology, 67(3):569 – 576, 2015.
[5] S. S. KETY. The theory and applications of the exchange of inert gas at the lungs and tissues. Pharmacological Reviews, 3(1):1–41, 1951.
[6] B. G. Tofts PS, Wicks DA. The mri measurement of nmr and physiological parameters in tissue to study disease process. Prog Clin Biol Res, 1991.
[7] Gunnar Brix;Wolfhard Semmler;Rüdiger Port;Lothar Schad;Günter Layer;Walter Lorenz. Pharmacokinetic parameters in cns gd-dtpa enhanced mr imaging. Journal of Computer Assisted Tomography, 1991.
[8] H. B. W. Larsson, M. Stubgaard, J. L. Frederiksen, M. Jensen, O. Henriksen, and O. B. Paulson. Quantitation of blood-brain barrier defect by magnetic resonance imaging and gadolinium-dtpa in patients with multiple sclerosis and brain tumors. Magnetic Resonance in Medicine, 16(1):117–131, 1990.
[9] P. S. Tofts and A. G. Kermode. Measurement of the blood-brain barrier permeability and leakage space using dynamic mr imaging. 1. fundamental concepts. Magnetic Resonance in Medicine, 17(2):357–367, 1991.
[10] K. M. Donahue, R. M. Weisskoff, and D. Burstein. Water diffusion and exchange as they influence contrast enhancement. Journal of Magnetic Resonance Imaging, 7(1):102–110, 1997.
[11] D. Flouri, D. Lesnic, and S. P. Sourbron. Fitting the two-compartment model in dce-mri by linear inversion. Magnetic Resonance in Medicine, 76(3):998–1006, 2016.
[12] R. Brun and F. Rademakers. Root — an object oriented data analysis framework. Nuclear Instruments and Methods in Physics Research Section A: Accelerators, Spectrometers, Detectors and Associated Equipment, 389(1):81 – 86, 1997. New Computing Techniques in Physics Research V.

# Infrastructure configuration

To reproduce this experimentation, we have used a MPI cluster consisting on three nodes and one frontend. All instances have been configured with a ubuntu 16 as SO, $1$ vCPU and $4$ GB of RAM. Also, to set all working nodes up when cluster starts, set the minimum and maximum number of working nodes to the same value, in our case to $3$.

# Experiment

Once the theoretical aspects of the experimentation have been discussed and the suggested infrastructure described, we can begin with the experiment itself. First of all, we need to install the local dependencies. The only requirement is the matplotlib package, which will be used to plot the results.

```python
from pip._internal import main as pipmain
pipmain(["install","--user", "matplotlib"])
```

## Cluster configuration

Now, to simplify the notebook reproduction, save the deployed cluster name and SO username in the variables "clusterName" and "username" respectively. In addition, we will define a list with the working nodes where the experimentation jobs will be executed. In our case, we deployed a cluster with 3 working nodes, so, our node list is "wn1,wn2,wn3" or "wn[1-3]"

```python
clusterName = "perfusion"
```

```python
username = "ubuntu"
```

```python
nodelist = "wn[1-3]"
```

Then, to use the APRICOT magic functions we must load them using the %reload_ext instruction

```python
%reload_ext apricot_magic
```

If the module has been loaded succesfully, we could see the status of our clusters using %apricot_ls command

```python
%apricot_ls
```

## Dependencies installation

Once the cluster has been successfully configured, the next step to run our code is to install all the required dependencies at the cluster. Our code requires an external library, the root package from CERN (https://root.cern.ch). As we selected Ubuntu16 as SO for our nodes, we have been download the corresponding package version, i.e.

root_v6.16.00.Linux-ubuntu16-x86_64-gcc5.4.tar.gz

from https://root.cern.ch/content/release-61600

For the shake of simplicity, we will suppose that the root tar file has been download at the same folder where this notebook is running. If you require a different version/distribution package, please, change the root tar name at the following variable,

```python
rootTarName = "root_v6.16.00.Linux-ubuntu16-x86_64-gcc5.4.tar.gz"
```

To install the dependencies, first we must wait untill all working nodes states have set to "idle". At this point, all working nodes are configured and ready to get jobs. To check their status, we can use %apricot_nodels instruction as following,

```python
%apricot_nodels $clusterName
```

Once all working nodes are at idle state, the next step consists to install the required packages from the repository. Notice that the name of the packages and the install command should be changed depending on the selected SO. To install the packages on all working nodes, we use the instruction %apricot_runOn with the node list created previously.

```python
%apricot_runOn $clusterName $nodelist sudo apt install --yes python python-dev python3-pip swig python-nibabel python-six python-numpy python3-numpy mpich
```

Now, install dependeces on the frontend node

```python
%apricot exec $clusterName sudo apt install --yes mpich python python-dev swig python3-pip python-numpy python3-numpy
```

```python
%apricot exec $clusterName pip install --user numpy
```

```python
%apricot exec $clusterName pip3 install --user mpi4py numpy nibabel
```

## Code compilation

At this point our frontend and working nodes have all the required dependencies, so is time to upload our program source code. This can easily be done using the %apricot_upload instruction,

```python
%apricot_upload $clusterName CAIF.dat PyWrapper.tar.xz fitPerfusion.py $rootTarName /home/$username
```

Also, we need to download the input image from our public S3 bucket using the following instruction,

```python
%apricot exec $clusterName curl https://grycap.s3.amazonaws.com/datasets/apricot/MRI/target.tar.xz --output target.tar.xz
```

The image is not stored at the github repository because its size. Now, all required data and files are at the cluster. Extract the tar files to compile our code,

```python
%apricot exec $clusterName tar -xf PyWrapper.tar.xz
```

```python
%apricot exec $clusterName tar -xf $rootTarName --directory PyWrapper/
```

Extract DICOM image

```python
%apricot exec $clusterName tar -xf target.tar.xz
```

Compile the code and create the python wrapper using the provided bash script "createWrapper.sh"

```python
%apricot exec $clusterName cd PyWrapper && bash createWrapper.sh
```

Get the compiled python wrapper

```python
%apricot exec $clusterName cp PyWrapper/_perfusion.so PyWrapper/perfusion.py .
```

Finally, if all the steps have been executed correctly, we should see the following files in the user home directory,

CAIF.dat
fitPerfusion.py
perfusion.py
\_perfusion.so
PyWrapper
PyWrapper.tar.xz
root_v6.16.00.Linux-ubuntu16-x86_64-gcc5.4.tar.gz  (or the name introduced at the varaible "rootTarName")
target
target.tar.xz

Execute a "ls" to check generated files,

```python
%apricot exec $clusterName ls
```

## Execution

At this point, both node types, frontend and workers, are ready for the execution. So, lets run our program "fitPerfusion.py" with the instruction %apricot_MPI. Notice that the program may report warnings when processes voxels with noise as signal. Notice also that the execution may take a few minutes.

```python
%apricot_MPI $clusterName 3 3 /home/ubuntu python3 /home/$username/fitPerfusion.py
```

Wait until the computation finish. The following instruction shows the actual job queue

```python
%apricot exec $clusterName squeue
```

Once the fit has concluded, download the results

```python
%apricot_download $clusterName results.dat .
```

## Results visualization

To visualize the results, first, read the downloaded file "results.dat" and extract the data of interest,

```python
#Read fit file
import numpy as np

with open("results.dat") as file:
    data = file.read()
    data = data.strip('\n').split('\n')

    ktrans  = np.zeros(shape=(len(data)),dtype=np.float)
    kep     = np.zeros(shape=(len(data)),dtype=np.float)
    ve      = np.zeros(shape=(len(data)),dtype=np.float)

    cont = 0
    for line in data:
        words = line.strip().split()
        ktrans[cont] = float(words[1])
        kep[cont]    = float(words[0])
        ve[cont]     = float(words[2])
        cont = cont+1

```

Reshape the results for plotting purposes

```python
ktrans = np.reshape(ktrans,(256,256,56))
kep    = np.reshape(kep,(256,256,56))
ve     = np.reshape(ve,(256,256,56))
```

Plot some $v_e$ planes from results

```python
import matplotlib.pyplot as plt

plane = 2

plt.figure(figsize=(40,40))

plt.subplot(2,2,1)
plt.xticks([])
plt.yticks([])

plt.imshow(np.rot90(ve[:,:,plane]), cmap='gray', vmin = 0, vmax = 1)

plt.subplot(2,2,2)
plt.xticks([])
plt.yticks([])

plt.imshow(np.rot90(ve[:,:,plane+10]), cmap='gray', vmin = 0, vmax = 1)

plt.subplot(2,2,3)
plt.xticks([])
plt.yticks([])

plt.imshow(np.rot90(ve[:,:,plane+20]), cmap='gray', vmin = 0, vmax = 1)

plt.subplot(2,2,4)
plt.xticks([])
plt.yticks([])

plt.imshow(np.rot90(ve[:,:,plane+30]), cmap='gray', vmin = 0, vmax = 1)

```

## Free resources

Finally, once our experimentation has been concluded, free the cluster resources using %apricot destroy instruction

```python
%apricot destroy $clusterName
```

## Conclusion

At this notebook, we have fitted a real MRI image using a perfusion model to obtain the transfer coefficient between blood plasma and the compartment, and the extracellular extravascular fractional volume. This results are fully reproducible using this notebook and some supported cloud provider to deploy the required infrastructure.


# FFM232, Klassisk fysik och vektorfält - Föreläsningsanteckningar

 **[Christian Forssén](http://fy.chalmers.se/subatom/tsp/), Institutionen för fysik, Chalmers, Göteborg, Sverige**

Date: **Sep 9, 2016**

# 2. Kroklinjiga koordinater

Allmänt behöver vi tre parametrar $u_1, u_2, u_3$ för att beskriva en godtycklig punkt i rummet. Jämför med *generaliserade koordinater* i analytisk mekanik. Vi kan då skriva ortsvektorn som $\vec{r}(u_1, u_2, u_3)$.

### Koordinatyta

för koordinat $i$: alla lösningar till $u_i = \mathrm{konstant}$.

### Koordinatkurva

den kurva som fås om en koordinat tillåts variera och de andra hålls konstanta.

Om vi då håller en av parametrarna, säg $u_1$, fix och låter $u_2$ och $u_3$ variera, så får vi en två-dimensionell yta, vilken vi kallar $u_1$-ytan. På samma sätt kan vi då definiera ytor för de andra koordinaterna. Två koordinatytor, till exempel de för koordinaterna $u_2$ och $u_3$, skär varandra längs en en-dimensionell kurva.  Längs denna kurva kommer då bara koordinaten $u_1$ att variera, så denna kurva är en koordinatkurva för $u_1$.

### Exempel:

I de cylindriska koordinaterna $\rho, \phi, z$ kan vi skriva ortsvektorn som $\vec{r} = \rho\cos \phi \hat{x} + \rho\sin \phi \hat{y} + z \hat{z}$.

Koordinatytorna för $\rho, \phi, z$ är då en cylinder med $z$-axeln som symmetriaxel och med radien $\rho$, ett plan som utgår från $z$-axeln och bildar en vinkel $\phi$ med $x$-axeln, samt ett plan parallellt med $xy$-planet och med $z$-koordinaten $z$.

Koordinatlinjerna för $\rho, \phi, z$ blir då en stråle som utgår från $z$-axeln och bildar vinkeln $\phi$ med $x$-axeln, en cirkel med radien $\rho$ och en linje parallell med $z$-axeln.

## Enhetsvektorer
Om vi nu studerar en liten förskjutning av ortsvektorn, $\mbox{d}\vec{r}$, så kan vi i och med att ortsvektorn är en funktion av $u_1, u_2, u_3$ skriva denna som

$$
\begin{equation}
  \mbox{d}\vec{r} = \frac{\partial \vec{r}}{\partial u_1} \mbox{d}u_1 +
\frac{\partial \vec{r}}{\partial u_2} \mbox{d}u_2 +
\frac{\partial \vec{r}}{\partial u_3} \mbox{d}u_3.
\end{equation}
$$

Tänk nu på att den partiella derivatan $\partial \vec{r}/\partial u_1$
är definierad som derivatan då vi håller $u_2$ och $u_3$ fixa.
Därför måste $\partial \vec{r}/\partial u_1$ vara en tangentvektor
till koordinatkurvan för $u_1$.  Vi kan då definiera en enhetsvektor
för $u_1$ som

$$
\begin{equation}
  \hat{e}_1 = \frac{1}{h_1} \frac{\partial \vec{r}}{\partial u_1},
\end{equation}
$$

där

$$
\begin{equation}
  h_1 = \left|\frac{\partial \vec{r}}{\partial u_1}\right|
\end{equation}
$$

kallas för skalfaktorn.  På samma sätt kan vi bestämma skalfaktorer och enhetsvektorer till $u_2$ och $u_3$. Förskjutningsvektorn $\mbox{d}\vec{r}$ kan vi nu skriva som

$$
\begin{equation}
  \mbox{d}\vec{r} = h_1\hat{e}_1 \mbox{d}u_1 + h_2\hat{e}_2\mbox{d}u_2 +
h_3\hat{e}_3\mbox{d}u_3.
\end{equation}
$$

### Alternativ definition

Ett alternativ till att använda de normerade tangentvektorerna som enhetsvektorer är att använda normalvektorerna till koordinatytorna. Betrakta t.ex.

$$
\begin{equation}
u_1 = u_1(x,y,z) = \mathrm{konstant}.
\end{equation}
$$

Detta motsvarar en nivåyta till ett skalärfält. Normalvektorn ges alltså av $\nabla u_1$.
Det gäller alltid att

$$
\begin{equation}
\nabla u_i \cdot \frac{\partial \vec{r}}{\partial u_j} = \delta_{ij}.
\end{equation}
$$

När vi inskränker oss till ortogonala system gäller dessutom att $\nabla u_i \parallel \frac{\partial \vec{r}}{\partial u_i}$. Notera dock att dessa vektorer i allmänhet kan ha *olika* längd. Faktum är att följande samband gäller för ortogonala system

$$
\begin{equation}
\hat{e}_i
= \frac{1}{h_i} \frac{\partial \vec{r}}{\partial u_i}
= h_i \nabla u_i.
\end{equation}
$$

### Exempel:

I cylindriska koordinater är $\vec{r} = (\rho\cos \phi, \rho\sin \phi, z)$.  Vi kan då beräkna

$$
\begin{equation}
  \frac{\partial \vec{r}}{\partial \rho} = \left(\cos \phi, \sin \phi, 0\right),
\end{equation}
$$

$$
\begin{equation}
  \frac{\partial \vec{r}}{\partial \phi} = \left(-\rho \sin\phi, \rho \cos \phi,
0\right),
\end{equation}
$$

$$
\begin{equation}
  \frac{\partial \vec{r}}{\partial z} = \left(0,0,1\right).
\end{equation}
$$

Skalfaktorerna blir då

$$
\begin{equation}
  h_\rho = \left(\cos^2\phi + \sin^2\phi\right)^{1/2} = 1,
\end{equation}
$$

$$
\begin{equation}
  h_\phi = \left(\rho^2 \cos^2\phi + \rho^2 \sin^2\phi\right)^{1/2} = \rho,
\end{equation}
$$

$$
\begin{equation}
  h_z = 1.
\end{equation}
$$

Enhetsvektorerna blir

$$
\begin{equation}
  \hat{\rho} = \left(\cos\phi, \sin\phi,0\right),
\end{equation}
$$

$$
\begin{equation}
  \hat{\phi} = \left(-\sin\phi, \cos\phi,0\right),
\end{equation}
$$

$$
\begin{equation}
  \hat{z} = \left(0,0,1\right).
\end{equation}
$$

Förskjutningsvektorn kan då skrivas som

$$
\begin{equation}
  \mbox{d}\vec{r} = \hat{\rho} \mbox{d}\rho + \rho \hat{\phi} \mbox{d} \phi + \hat{z} \mbox{d}z.
\end{equation}
$$

I fortsättningen skall vi begränsa oss till koordinatsystem med ortogonala enhetsvektorer, dvs

$$
\begin{equation}
\hat{e}_i \cdot \hat{e}_j = \delta_{ij} = \left\{
\begin{array}{ll}
1 & \,\mbox{om}\,\, i = j \\
0 & \,\mbox{annars}\\
\end{array}\right.
\end{equation}
$$

där vi passat på att introducera Kroneckers delta, $\delta_{ij}$.

Vi skall också anta att enhetsvektorerna bildar ett högersystem

$$
\begin{equation}
\hat{e}_1 \times \hat{e}_2 = \hat{e}_3
\end{equation}
$$

*Visa att enhetsvektorerna i de cylindriska koordinaterna uppfyller dessa villkor.*

Vi kan nu härleda några användbara samband som båglängden längs en kurva

$$
\begin{equation}
\mbox{d}s^2 = \mbox{d}\vec{r}\cdot \mbox{d}\vec{r} = h_1^2\mbox{d}u_1^2 + h_2^2
\mbox{d}u_2^2 + h_3^2 \mbox{d}u_3^2.
\end{equation}
$$

Betrakta ovanstående båglängd för fallet då $du_2=du_3=0$. Det står då klart att vi kan tolka $h_1 du_1$ som båglängden $ds_1$, dvs som en infinitesimal förflyttning i $u_1$-riktningen. Notera därför att $h_i du_i$ alltid måste ha enheten längd.

Ett ytelement $\mbox{d}\vec{S}_1$ på koordinatytan $u_1$ är en rektangel som  genereras av $\mbox{d}u_2$ och $\mbox{d}u_3$. Rektangelns sidor har då längderna $h_2\mbox{d}u_2$ och $h_3\mbox{d}u_3$.  Ytelementet blir

$$
\begin{equation}
  \mbox{d}\vec{S}_1 =  \hat{e}_1 h_2 h_3 \mbox{d}u_2 \mbox{d}u_3,
\end{equation}
$$

och på samma sätt kan vi beräkna ytelementen på koordinatytorna för $u_2$ och $u_3$.

Analogt kan vi beräkna volymelementet som genereras av  $\mbox{d}u_1$, $\mbox{d}u_2$ och $\mbox{d}u_3$, vilket blir

$$
\begin{equation}
  \mbox{d}V = h_1 h_2 h_3 \mbox{d}u_1 \mbox{d}u_2 \mbox{d}u_3.
\end{equation}
$$

### Exempel:

Bågelementet i cylindriska koordinater blir

$$
\begin{equation}
  \mbox{d}s^2 = \mbox{d}\rho^2 + \rho^2\mbox{d}\theta^2 + \mbox{d}z^2.
\end{equation}
$$

Ett ytelement på $\rho$-ytan skrives

$$
\begin{equation}
  \mbox{d}\vec{S}_\rho = \hat{e}_\rho \rho \mbox{d}\phi\mbox{d}z,
\end{equation}
$$

på $\phi$-ytan

$$
\begin{equation}
  \mbox{d}\vec{S}_\phi = \hat{e}_\phi \mbox{d}\rho\mbox{z}
\end{equation}
$$

och på $z$-ytan

$$
\begin{equation}
  \mbox{d}\vec{S}_z = \hat{e}_z \rho\mbox{d}\rho \mbox{d}\phi.
\end{equation}
$$

Volymelementet kan vi skriva som

$$
\begin{equation}
  \mbox{d}V = \rho\mbox{d}\rho \mbox{d}\phi\mbox{d}z.
\end{equation}
$$

## Vektoroperatorer i kroklinjiga koordinater

### Gradient

Betrakta ett skalärt fält $\phi$.  Om vi förflyttar oss en sträcka $\mbox{d}\vec{r}$ så förändras $\phi$

$$
\begin{equation}
  \mbox{d}\phi = \nabla \phi \cdot \mbox{d}\vec{r}.
\end{equation}
$$

Förflyttningen kan vi i de nya koordinaterna skriva som

$$
\begin{equation}
  \mbox{d}\vec{r} = h_1 \hat{e}_1 \mbox{d}u_1 + h_2 \hat{e}_2 \mbox{d}u_2
+ h_3 \hat{e}_3 \mbox{d}u_3.
\end{equation}
$$

Om vi skriver $\phi$ som en funktion av $u_1, u_2$ och $u_3$ får vi

$$
\begin{equation}
  \mbox{d}\phi = \frac{\partial \phi}{\partial u_1}\mbox{d}u_1 +
\frac{\partial \phi}{\partial u_2}\mbox{d}u_2 +
\frac{\partial \phi}{\partial u_3}\mbox{d}u_3 =
\frac{1}{h_1} \frac{\partial \phi}{\partial u_1} h_1 \mbox{d}u_1 +
\frac{1}{h_2} \frac{\partial \phi}{\partial u_2} h_2 \mbox{d}u_2 +
\frac{1}{h_3} \frac{\partial \phi}{\partial u_3} h_3 \mbox{d}u_3 \nonumber
\end{equation}
$$

$$
\begin{equation}
 =
\left(\frac{1}{h_1} \frac{\partial \phi}{\partial u_1} \hat{e}_1 +
\frac{1}{h_2} \frac{\partial \phi}{\partial u_2} \hat{e}_2 +
\frac{1}{h_3} \frac{\partial \phi}{\partial u_3} \hat{e}_3\right) \cdot
\mbox{d}\vec{r}
\end{equation}
$$

Då kan vi identifiera uttrycket inom parentesen som gradienten i de nya koordinaterna $u_1, u_2, u_3$

$$
\begin{equation}
  \nabla \phi = \frac{1}{h_1} \frac{\partial \phi}{\partial u_1} \hat{e}_1
+ \frac{1}{h_2}
\frac{\partial \phi}{\partial u_2} \hat{e}_2
+ \frac{1}{h_3} \frac{\partial \phi}{\partial u_3} \hat{e}_3.
\end{equation}
$$

### Gradient i cylindriska koordinater:

I cylindriska koordinater blir gradienten

$$
\begin{equation}
  \nabla f = \frac{\partial f}{\partial \rho} + \frac{1}{\rho}
\frac{\partial f}{\partial \phi} + \frac{\partial f}{\partial z}.
\end{equation}
$$

### Exempel

Ett skalärfält är givet i Cartesiska koordinater

$$
\begin{equation}
\beta = x^2 + y^2.
\end{equation}
$$

Motsvarande skalärfält i plana polärkoordinater blir

$$
\begin{equation}
\beta = r^2\cos^2\theta + r^2 \sin^2\theta = r^2.
\end{equation}
$$

Gradienten i Cartesiska koordinater blir

$$
\begin{equation}
\nabla \beta = \hat{x} \partial_x \beta + \hat{y} \partial_y \beta = 2 (x \hat{x} + y \hat{y}).
\end{equation}
$$

Medan i plana polärkoordinater blir den

$$
\begin{equation}
\nabla \beta = \hat{e}_r \partial_r + \hat{e}_\theta \frac{1}{r} \partial_\theta \beta = 2 r \hat{e}_r.
\end{equation}
$$

Eftersom $x \hat{x} + y \hat{y} = r \hat{e}_r$ är det uppenbart att detta är samma vektor!

## Tentauppgift 2010-08-26: 1b

 För vilka värden på $\alpha,\beta,\gamma$ har det tvådimensionella koordinatsystemet med koordinater $\xi$ och $\eta$, givna av

$$
\begin{equation}
\xi = x^2 - y^2
\end{equation}
$$

$$
\begin{equation}
\eta = \alpha x^2 + \beta x y + \gamma y^2
\end{equation}
$$

ortogonala basvektorer?

### Lösning

Vi kan konstruera basvektorer på två sätt:
* $\hat{e}_i \propto \frac{\partial \vec{r}}{\partial u_i}$

* $\hat{e}_i \propto \nabla u_i$

Det första sättet innebär att vi behöver räkna ut storheterna $\frac{\partial x}{\partial u_i}$ och $\frac{\partial y}{\partial u_i}$, dvs vi behöver veta $x=x(\xi,\eta),\; y=y(\xi,\eta)$. Vi skulle behöva invertera det givna koordinatsambandet.

Det andra sättet kräver istället $\frac{\partial \xi}{\partial x}$ och $\frac{\partial \xi}{\partial y}$ (samt motsvarande för $\eta$) och detta blir enkelt med de givna koordinattransformationerna. Vi får

$$
\begin{equation}
\nabla \xi = 2x \hat{x} - 2y \hat{y}
\end{equation}
$$

$$
\begin{equation}
\nabla \eta = (2 \alpha x + \beta y)\hat{x} + (\beta x + 2 \gamma y) \hat{y}
\end{equation}
$$

vilket ger

$$
\begin{equation}
\nabla \xi \cdot \nabla \eta = 2x (2 \alpha x + \beta y) - 2 y (\beta x + 2 \gamma y)
= 4 \alpha x^2 - 4 \gamma y^2 = 0.
\end{equation}
$$

Detta innebär att vi måste ha $\alpha = \gamma = 0$, medan $\beta$ är godtyckligt.

0

<
<
<
!
!
C
O
D
E
_
B
L
O
C
K

p
y
c
o
d

```
p.figure(figsize=(6,6))
CS = p.contour(x,y,xi,5,colors='k')
p.clabel(CS, inline=1, fontsize=10)
p.text(0.5,1.3,r'$\xi$-yta',color='k',rotation=45)
CS = p.contour(x,y,eta,5,colors='r')
p.clabel(CS, inline=1, fontsize=10)
p.text(1,0.05,r'$\eta$-yta',color='r')
p.xlabel(r'$x$')
p.ylabel(r'$y$')
```