<br>

<i>This program was contributed by Wolfgang Bangerth and Luca Heltai, using
data provided by D. Sarah Stamps.</i>

@note This program elaborates on concepts of geometry and the classes that
implement it. These classes are grouped into the documentation module on @ref
manifold "Manifold description for triangulations". See there for additional
information.


<a name="Intro"></a>
<h1>Introduction</h1>

Partial differential equations for realistic problems are often posed on
domains with complicated geometries. To provide just a few examples, consider
these cases:
- Among the two arguably most important industrial applications for the finite
  element method, aerodynamics and more generally fluid dynamics is
  one. Computer simulations today are used in the design of every airplane,
  car, train and ship. The domain in which the partial differential equation
  is posed is, in these cases, the air surrounding the plane with its wings,
  flaps and engines; the air surrounding the car with its wheel, wheel wells,
  mirrors and, in the case of race cars, all sorts of aerodynamic equipment;
  the air surrounding the train with its wheels and gaps between cars. In the
  case of ships, the domain is the water surrounding the ship with its rudders
  and propellers.
- The other of the two big applications of the finite element method is
  structural engineering in which the domains are bridges, airplane nacelles
  and wings, and other solid bodies of often complicated shapes.
- Finite element modeling is also often used to describe the generation and
  propagation of earthquake waves. In these cases, one needs to accurately
  represent the geometry of faults in the Earth crust. Since faults intersect,
  dip at angles, and are often not completely straight, domains are frequently
  very complex.
One could cite many more examples of complicated geometries in which one wants
to pose and solve a partial differential equation. What this shows is that the
"real" world is much more complicated than what we have shown in almost all of
the tutorial programs preceding this one. 

This program is therefore devoted to showing how one deals with complex
geometries using a concrete application. In particular, what it shows is how
we make a mesh fit the domain we want to solve on. On the other hand, what the
program does not show is how to create a coarse for a domain. The process to
arrive at a coarse mesh is called "mesh generation" and there are a number of
high-quality programs that do this much better than we could ever
implement. However, deal.II does have the ability to read in meshes in many
formats generated by mesh generators and then make them fit a given shape,
either by deforming a mesh or refining it a number of times until it fits. The
deal.II Frequently Asked Questions page referenced from http://www.dealii.org/
provides resources to mesh generators.


<h2>Where geometry and meshes intersect</h2>

Let us assume that you have a complex domain and that you already have a
coarse mesh that somehow represents the general features of the domain. Then
there are two situations in which it is necessary to describe to a deal.II
program the details of your geometry:

- Mesh refinement: Whenever a cell is refined, it is necessary to introduce
  new vertices in the Triangulation. In the simplest case, one assumes that
  the objects that make up the Triangulation are straight line segments, a
  bi-linear surface or a tri-linear volume. The next vertex is then simply put
  into the middle of the old ones. However, for curved boundaries or if we
  want to solve a PDE on a curved, lower-dimensional manifold embedded in a
  higher-dimensional space, this is insufficient since it will not respect the
  actual geometry. We will therefore have to tell Triangulation where to put
  new points.

- Integration: When using higher order finite element methods, it is often
  necessary to compute integrals using curved approximations of the boundary,
  i.e., describe each edge or face of cells as curves, instead of straight
  line segments or bilinear patches. The same is, of course, true when
  integrating boundary terms (e.g., inhomogenous Neumann boundary
  conditions). For the purpose of integration, the various Mapping classes
  then provide the transformation from the reference cell to the actual cell.

In both cases, we need a way to provide information about the geometry of the
domain at the level of an individual cell, its faces and edges. This is where
the Manifold class comes into play. Manifold is an abstract base class that
only defines an interface by which the Triangulation and Mapping classes can
query geometric information about the domain. Conceptually, Manifold sees the
world in a way not dissimilar to how the mathematical subdiscipline geometry
sees it: a domain is essentially just a collection of points that is somehow
equipped with the notion of a distance between points so that we can obtain a
point "in the middle" of some other points.

deal.II provides a number of classes that implement the interface provided by
Manifold for a variety of common geometries. On the other hand, in this
program we will consider only a very common and much simpler case, namely the
situation where (a part of) the domain we want to solve on can be described by
transforming a much simpler domain (we will call this the "reference domain").
In the language of mathematics, this means
that the (part of the) domain is a <a
href="http://en.wikipedia.org/wiki/Chart_%28topology%29">chart</a>. Charts are
described by a smooth function that maps from the simpler domain to the chart
(the "push-forward" function) and its inverse (the "pull-back" function). If
the domain as a whole is not a chart (e.g., the surface of a sphere), then it
can often be described as a collection of charts (e.g., the northern
hemisphere and the southern hemisphere are each charts) and the domain can then
be describe by an <a
href="http://en.wikipedia.org/wiki/Atlas_%28topology%29">atlas</a>. 

If a domain can be decomposed into an atlas, all we need to do is provide the
pull-back and push-forward functions for each of the charts. In deal.II, this
means providing a class derived from ChartManifold, and this is precisely what
we will do in this program.


<h2>The example case</h2>

To illustrate how one describes geometries using charts in deal.II, we will
consider a case that originates in an application of the <a
href="http://aspect.dealii.org">ASPECT mantle convection code</a>, using a
data set provided by D. Sarah Stamps. In the concrete application, we were
interested in describing flow in the Earth mantle under the <a
href="http://en.wikipedia.org/wiki/East_African_rift">East African Rift</a>, a
zone where two continental plates drift apart. Not to beat around the bush,
the geometry we want to describe looks like this:

<img src="https://www.dealii.org/images/steps/developer/step-53.topo.png" alt="">

In particular, though you cannot see this here, the top surface is not
just colored by the elevation but is, in fact, deformed to follow the 
correct topography.
While the actual application is not relevant here, the geometry is. The domain
we are interested in is a part of the Earth that ranges from the surface to a
depth of 500km, from 26 to 35 degrees East of the Greenwich meridian, and from
5 degrees North of the equator to 10 degrees South.

This description of the geometry suggests to start with a box 
$\hat U=[26,35]\times[-10,5]\times[-500000,0]$ (measured in degrees,
degrees, and meters) and to provide a map $\varphi$ so
that $\varphi^{-1}(\hat U)=\Omega$ where $\Omega$ is the domain we
seek. $(\Omega,\varphi)$ is then a chart, $\varphi$ the pull-back operator, and
$\varphi^{-1}$ the push-forward operator. If we need a point $q$ that is the
"average" of other points $q_i\in\Omega$, the ChartManifold class then first
applies the pull-back to obtain $\hat q_i=\varphi(q_i)$, averages these to a
point $\hat p$ and then computes $p=\varphi^{-1}(\hat p)$.

Our goal here is therefore to implement a class that describes $\varphi$ and
$\varphi^{-1}$. If Earth was a sphere, then this would not be difficult: if we
denote by $(\hat \phi,\hat \theta,\hat d)$ the points of $\hat U$ (i.e.,
longitude counted eastward, latitude counted northward, and elevation relative
to zero depth), then
@f[
  \mathbf x = \varphi^{-1}(\hat \phi,\hat \theta,\hat d)
  = (R+\hat d) (\cos\hat \phi\cos\hat \theta, \sin\hat \phi\cos\hat \theta, \sin\hat \theta)^T
@f]
provides coordinates in a Cartesian coordinate system, where $R$ is the radius
of the sphere. However, the Earth is not a sphere:

<ol>
<li> It is flattened at the poles and larger at the equator: the semi-major axis
  is approximately 22km longer than the semi-minor axis. We will account for
  this using the <a href="http://en.wikipedia.org/wiki/WGS84">WGS 84</a>
  reference standard for the Earth shape. The formula used in WGS 84 to obtain
  a position in Cartesian coordinates from longitude, latitude, and elevation
  is 
@f[
  \mathbf x = \varphi_\text{WGS84}^{-1}(\phi,\theta,d)
  = \left(
    \begin{array}{c}
     (\bar R(\theta)+d) \cos\phi\cos\theta, \\
     (\bar R(\theta)+d) \sin\phi\cos\theta, \\
     ((1-e^2)\bar R(\theta)+d) \sin\theta
    \end{array}
    \right),
@f]
  where $\bar R(\theta)=\frac{R}{\sqrt{1-(e \sin\theta)^2}}$, and radius and
  ellipticity are given by $R=6378137\text{m}, e=0.081819190842622$. In this formula, 
  we assume that the arguments to sines and cosines are evaluated in degree, not
  radians (though we will have to change this assumption in the code).

<li> It has topography in the form of mountains and valleys. We will account for
  this using real topography data (see below for a description of where
  this data comes from). Using this data set, we can look up elevations on a
  latitude-longitude mesh laid over the surface of the Earth. Starting with
  the box $\hat U=[26,35]\times[-10,5]\times[-500000,0]$, we will therefore 
  first stretch it in vertical direction before handing it off to the WGS 84
  function: if $h(\hat\phi,\hat\theta)$ is the height at longitude $\hat\phi$
  and latitude $\hat\theta$, then we define
@f[
  (\phi,\theta,d) =
  \varphi_\text{topo}^{-1}(\hat\phi,\hat\theta,\hat d)
  = \left(
      \hat\phi,
      \hat\theta,
      \hat d + \frac{\hat d+500000}{500000}h(\hat\phi,\hat\theta)
    \right).
@f]
  Using this function, the top surface of the box $\hat U$ is displaced to the
  correct topography, the bottom surface remains where it was, and points in
  between are linearly interpolated.
</ol>

Using these two functions, we can then define the entire push-forward function
$\varphi^{-1}: \hat U \rightarrow \Omega$ as
@f[
  \mathbf x 
  = 
  \varphi^{-1}(\hat\phi,\hat\theta,\hat d)
  =
  \varphi_\text{WGS84}^{-1}(\varphi_\text{topo}^{-1}(\hat\phi,\hat\theta,\hat d)).
@f]
In addition, we will have to define the inverse of this function, the
pull-back operation, which we can write as
@f[
  (\hat\phi,\hat\theta,\hat d)
  = 
  \varphi(\mathbf x)
  =
  \varphi_\text{topo}(\varphi_\text{WGS84}(\mathbf x)).
@f]
We can obtain one of the components of this function by inverting the formula above:
@f[
  (\hat\phi,\hat\theta,\hat d) =
  \varphi_\text{topo}(\phi,\theta,d)
  = \left(
      \phi,
      \theta,
      500000\frac{d-h(\phi,\theta)}{500000+h(\phi,\theta)}
    \right).
@f]
Computing $\varphi_\text{WGS84}(\mathbf x)$ is also possible though a lot more
awkward. We won't show the formula here but instead only provide the implementation
in the program.


<h2>Implementation</h2>

There are a number of issues we need to address in the program. At the largest scale, 
we need to write a class that implements the interface of ChartManifold. This involves
a function <code>push_forward()</code> that takes a point
in the reference domain $\hat U$ and transform it into real space using the function
$\varphi^{-1}$ outlined above, and its inverse function <code>pull_back()</code>
implementing $\varphi$. We will do so in the <code>AfricaGeometry</code> class below
that looks, in essence, like this:
@code
  class AfricaGeometry : public ChartManifold<3,3>
  {
  public:
    virtual
    Point<3>
    pull_back(const Point<3> &space_point) const;

    virtual
    Point<3>
    push_forward(const Point<3> &chart_point) const;

  private:
    ... some member variables and other member functions...;
  };
@endcode

The transformations above have two parts: the WGS 84 transformations and the topography
transformation. Consequently, the <code>AfricaGeometry</code> class will have 
additional (non-virtual) member functions 
<code>AfricaGeometry::push_forward_wgs84()</code> and
<code>AfricaGeometry::push_forward_topo()</code> that implement these two pieces, and
corresponding pull back functions.

The WGS 84 transformation functions are not particularly interesting (even though the
formulas they implement are impressive). The more interesting part is the topography
transformation. Recall that for this, we needed to evaluate the elevation function
$h(\hat\phi,\hat\theta)$. There is of course no formula for this: Earth is what it is,
the best one can do is look up the altitude from some table. This is, in fact what we
will do.

The data we use was originally created by the  <a
href="http://en.wikipedia.org/wiki/Shuttle_Radar_Topography_Mission">Shuttle
Radar Topography Mission</a>, was downloaded from the US Geologic Survey
(USGS) and processed by D. Sarah Stamps who also wrote the initial version of
the WGS 84 transformation functions. The topography data so processed is
stored in a file <code>topography.txt.gz</code> that, when unpacked
looks like this:
@code
6.983333 25.000000 700
6.983333 25.016667 692
6.983333 25.033333 701
6.983333 25.050000 695
6.983333 25.066667 710
6.983333 25.083333 702
...
-11.983333 35.950000 707
-11.983333 35.966667 687
-11.983333 35.983333 659
@endcode
The data is formatted as <code>latitude longitude elevation</code> where the first two
columns are provided in degrees North of the equator and degrees East of the Greenwich
meridian. The final column is given in meters above the WGS 84 zero elevation. 

In the transformation functions, we need to evaluate $h(\hat\phi,\hat\theta)$ for a given
longitude $\hat\phi$ and latitude $\hat\theta$. In general, this data point will not be
available and we will have to interpolate between adjacent data points. Writing such an
interpolation routine is not particularly difficult, but it is a bit tedious and error
prone. Fortunately, we can somehow shoehorn this data set into an existing class:
Functions::InterpolatedUniformGridData . Unfortunately, the class does not fit the bill
quite exactly and so we need to work around it a bit. The problem comes from the way
we initialize this class: in its simplest form, it takes a stream of values that it 
assumes form an equispaced mesh in the $x-y$ plane (or, here, the $\phi-\theta$ plane).
Which is what they do here, sort of: they are ordered latitude first, longitude second;
and more awkwardly, the first column starts at the largest values and counts down,
rather than the usual other way around. 

Now, while tutorial programs are meant to illustrate how to code with deal.II, they do
not necessarily have to satisfy the same quality standards as one would have to do
with production codes. In a production code, we would write a function that reads the
data and (i) automatically determines the extents of the first and second column,
(ii) automatically determines the number of data points in each direction, (iii) does
the interpolation regardless of the order in which data is arranged, if necessary
by switching the order between reading and presenting it to the
Functions::InterpolatedUniformGridData class.

On the other hand, tutorial programs are best if they are short and demonstrate key
points rather than dwell on unimportant aspects and, thereby, obscure what we really
want to show. Consequently, we will allow ourselves a bit of leeway:
- since this program is intended solely for a particular geometry around the area 
  of the East-African rift and since this is precisely the area described by the data 
  file, we will hardcode in the program that there are 
  $1139\times 660$ pieces of data;
- we will hardcode the boundaries of the data 
  $[-11.98333^\circ,6.983333^\circ]\times[25^\circ,35.98333^\circ]$;
- we will lie to the Functions::InterpolatedUniformGridData class: the class will
  only see the data in the last column of this data file, and we will pretend that
  the data is arranged in a way that there are 1139 data points in the first
  coordinate direction that are arranged in <i>ascending</i> order but in an
  interval $[-6.983333^\circ,11.98333^\circ]$ (not the negated bounds). Then,
  when we need to look something up for a latitude $\hat\theta$, we can ask the
  interpolating table class for a value at $-\hat\theta$. With this little
  trick, we can avoid having to switch around the order of data as read from
  file.
  
All of this then calls for a class that essentially looks like this:
@code
  class AfricaTopography
  {
  public:
    AfricaTopography ()
      :
      topography_data (...initialize somehow...)
    {}

    double value (const double lon, const double lat) const
    {
      return topography_data.value (Point<2>(-lat * 180/numbers::PI,
                                             lon * 180/numbers::PI));
    }

  private:
    const Functions::InterpolatedUniformGridData<2> topography_data;
  };
@endcode

Note how the <code>value()</code> function negates the latitude. It also switches
from the format $\phi,\theta$ that we use everywhere else to the latitude-longitude
format used in the table. Finally, it takes its arguments in radians as that is what
we do everywhere else in the program, but then converts them to the degree-based
system used for table lookup. As you will see in the implementation below, the function
has a few more (static) member functions that we will call in the initialization
of the <code>topography_data</code> member variable: the class type of this variable
has a constructor that allows us to set everything right at construction time,
rather than having to fill data later on, but this constructor takes a number of
objects that can't be constructed in-place (at least not in C++98). Consequently,
the construction of each of the objects we want to pass in the initialization happens
in a number of static member functions.

Having discussed the general outline of how we want to implement things, let us go
to the program and show how it is done in practice.

