{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Step-53"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is a replica of [step-53](https://www.dealii.org/current/doxygen/deal.II/step_53.html) C++ turorial program. However, here we will use the deal.II Python interface to implement the functionality of the original tutorial. \n",
    "\n",
    "Not all of the material is replicated since some parts of the original C++ tutorial are only relevant when using C++. Therefore, it is recommended that you first go through the original C++ tutorial to see all the details covered there.\n",
    "\n",
    "*This notebook is contributed by Alexander Grayver, 2020*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Python initialization"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Provided the deal.II library was compiled with the python wrappers, we can import the module **PyDealII**. Note that it is only a shell and importing it will only allow you to call\n",
    "```python \n",
    "help(PyDealII)\n",
    "``` \n",
    "**PyDealII** is composed of two libraries: \n",
    " - **PyDealII.Debug** which uses the debug version of **deal.II**\n",
    " - **PyDealII.Release** which uses the release version of **deal.II**\n",
    " \n",
    "Let us import the release version of the library and give it the shortname **dealii**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import PyDealII.Release as dealii"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In addition, we will import a few widely used python packages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import matplotlib\n",
    "import numpy as np\n",
    "import re\n",
    "import math\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Partial differential equations for realistic problems are often posed on\n",
    "domains with complicated geometries. To provide just a few examples, consider\n",
    "these cases:\n",
    "- Among the two arguably most important industrial applications for the finite\n",
    "  element method, aerodynamics and more generally fluid dynamics is\n",
    "  one. Computer simulations today are used in the design of every airplane,\n",
    "  car, train and ship. The domain in which the partial differential equation\n",
    "  is posed is, in these cases, the air surrounding the plane with its wings,\n",
    "  flaps and engines; the air surrounding the car with its wheel, wheel wells,\n",
    "  mirrors and, in the case of race cars, all sorts of aerodynamic equipment;\n",
    "  the air surrounding the train with its wheels and gaps between cars. In the\n",
    "  case of ships, the domain is the water surrounding the ship with its rudders\n",
    "  and propellers.\n",
    "- The other of the two big applications of the finite element method is\n",
    "  structural engineering in which the domains are bridges, airplane nacelles\n",
    "  and wings, and other solid bodies of often complicated shapes.\n",
    "- Finite element modeling is also often used to describe the generation and\n",
    "  propagation of earthquake waves. In these cases, one needs to accurately\n",
    "  represent the geometry of faults in the Earth crust. Since faults intersect,\n",
    "  dip at angles, and are often not completely straight, domains are frequently\n",
    "  very complex.\n",
    "One could cite many more examples of complicated geometries in which one wants\n",
    "to pose and solve a partial differential equation. What this shows is that the\n",
    "\"real\" world is much more complicated than what we have shown in almost all of\n",
    "the tutorial programs preceding this one.\n",
    "\n",
    "This program is therefore devoted to showing how one deals with complex\n",
    "geometries using a concrete application. In particular, what it shows is how\n",
    "we make a mesh fit the domain we want to solve on. On the other hand, what the\n",
    "program does not show is how to create a coarse for a domain. The process to\n",
    "arrive at a coarse mesh is called \"mesh generation\" and there are a number of\n",
    "high-quality programs that do this much better than we could ever\n",
    "implement. However, deal.II does have the ability to read in meshes in many\n",
    "formats generated by mesh generators and then make them fit a given shape,\n",
    "either by deforming a mesh or refining it a number of times until it fits. The\n",
    "deal.II Frequently Asked Questions page referenced from http://www.dealii.org/\n",
    "provides resources to mesh generators.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Where geometry and meshes intersect"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us assume that you have a complex domain and that you already have a\n",
    "coarse mesh that somehow represents the general features of the domain. Then\n",
    "there are two situations in which it is necessary to describe to a deal.II\n",
    "program the details of your geometry:\n",
    "\n",
    "- Mesh refinement: Whenever a cell is refined, it is necessary to introduce\n",
    "  new vertices in the [Triangulation](https://www.dealii.org/current/doxygen/deal.II/classTriangulation.html). \n",
    "  In the simplest case, one assumes that\n",
    "  the objects that make up the [Triangulation](https://www.dealii.org/current/doxygen/deal.II/classTriangulation.html) are straight line segments, a\n",
    "  bi-linear surface or a tri-linear volume. The next vertex is then simply put\n",
    "  into the middle of the old ones. However, for curved boundaries or if we\n",
    "  want to solve a PDE on a curved, lower-dimensional manifold embedded in a\n",
    "  higher-dimensional space, this is insufficient since it will not respect the\n",
    "  actual geometry. We will therefore have to tell [Triangulation](https://www.dealii.org/current/doxygen/deal.II/classTriangulation.html) where to put\n",
    "  new points.\n",
    "\n",
    "- Integration: When using higher order finite element methods, it is often\n",
    "  necessary to compute integrals using curved approximations of the boundary,\n",
    "  i.e., describe each edge or face of cells as curves, instead of straight\n",
    "  line segments or bilinear patches. The same is, of course, true when\n",
    "  integrating boundary terms (e.g., inhomogeneous Neumann boundary\n",
    "  conditions). For the purpose of integration, the various [Mapping](https://www.dealii.org/current/doxygen/deal.II/classMapping.html) classes\n",
    "  then provide the transformation from the reference cell to the actual cell.\n",
    "\n",
    "In both cases, we need a way to provide information about the geometry of the\n",
    "domain at the level of an individual cell, its faces and edges. This is where\n",
    "the Manifold class comes into play. Manifold is an abstract base class that\n",
    "only defines an interface by which the [Triangulation](https://www.dealii.org/current/doxygen/deal.II/classTriangulation.html) and [Mapping](https://www.dealii.org/current/doxygen/deal.II/classMapping.html) classes can\n",
    "query geometric information about the domain. Conceptually, [Manifold](https://www.dealii.org/current/doxygen/deal.II/classManifold.html) sees the\n",
    "world in a way not dissimilar to how the mathematical subdiscipline geometry\n",
    "sees it: a domain is essentially just a collection of points that is somehow\n",
    "equipped with the notion of a distance between points so that we can obtain a\n",
    "point \"in the middle\" of some other points.\n",
    "\n",
    "deal.II provides a number of classes that implement the interface provided by\n",
    "[Manifold](https://www.dealii.org/current/doxygen/deal.II/classManifold.html) for a variety of common geometries. On the other hand, in this\n",
    "program we will consider only a very common and much simpler case, namely the\n",
    "situation where (a part of) the domain we want to solve on can be described by\n",
    "transforming a much simpler domain (we will call this the \"reference domain\").\n",
    "In the language of mathematics, this means\n",
    "that the (part of the) domain is a <a\n",
    "href=\"http://en.wikipedia.org/wiki/Chart_%28topology%29\">chart</a>. Charts are\n",
    "described by a smooth function that maps from the simpler domain to the chart\n",
    "(the \"push-forward\" function) and its inverse (the \"pull-back\" function). If\n",
    "the domain as a whole is not a chart (e.g., the surface of a sphere), then it\n",
    "can often be described as a collection of charts (e.g., the northern\n",
    "hemisphere and the southern hemisphere are each charts) and the domain can then\n",
    "be describe by an <a\n",
    "href=\"http://en.wikipedia.org/wiki/Atlas_%28topology%29\">atlas</a>.\n",
    "\n",
    "If a domain can be decomposed into an atlas, all we need to do is provide the\n",
    "pull-back and push-forward functions for each of the charts. In deal.II, this\n",
    "means providing a class derived from [ChartManifold](https://www.dealii.org/current/doxygen/deal.II/classChartManifold.html), and this is precisely what\n",
    "we will do in this program."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### The example case"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To illustrate how one describes geometries using charts in deal.II, we will consider a case that originates in an application of the [ASPECT](https://aspect.geodynamics.org/) mantle convection code, 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 [East African Rift](http://en.wikipedia.org/wiki/East_African_rift), a zone where two continental plates drift apart. Not to beat around the bush, the geometry we want to describe looks like this:\n",
    "![Image](https://www.dealii.org/images/steps/developer/step-53.topo.png)\n",
    "\n",
    "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.\n",
    "\n",
    "This description of the geometry suggests to start with a box\n",
    "$\\hat U=[26,35]\\times[-10,5]\\times[-500000,0]$ (measured in degrees,\n",
    "degrees, and meters) and to provide a map $\\varphi$ so\n",
    "that $\\varphi^{-1}(\\hat U)=\\Omega$ where $\\Omega$ is the domain we\n",
    "seek. $(\\Omega,\\varphi)$ is then a chart, $\\varphi$ the pull-back operator, and\n",
    "$\\varphi^{-1}$ the push-forward operator. If we need a point $q$ that is the\n",
    "\"average\" of other points $q_i\\in\\Omega$, the [ChartManifold](https://www.dealii.org/current/doxygen/deal.II/classChartManifold.html) class then first\n",
    "applies the pull-back to obtain $\\hat q_i=\\varphi(q_i)$, averages these to a\n",
    "point $\\hat p$ and then computes $p=\\varphi^{-1}(\\hat p)$.\n",
    "\n",
    "Our goal here is therefore to implement a class that describes $\\varphi$ and\n",
    "$\\varphi^{-1}$. If Earth was a sphere, then this would not be difficult: if we\n",
    "denote by $(\\hat \\phi,\\hat \\theta,\\hat d)$ the points of $\\hat U$ (i.e.,\n",
    "longitude counted eastward, latitude counted northward, and elevation relative\n",
    "to zero depth), then $$\\mathbf x = \\varphi^{-1}(\\hat \\phi,\\hat \\theta,\\hat d)\n",
    "  = (R+\\hat d) (\\cos\\hat \\phi\\cos\\hat \\theta, \\sin\\hat \\phi\\cos\\hat \\theta, \\sin\\hat \\theta)^T$$ provides coordinates in a Cartesian coordinate system, where $R$ is the radius\n",
    "of the sphere. However, the Earth is not a sphere:\n",
    "\n",
    "1. 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 [WGS84](http://en.wikipedia.org/wiki/WGS84) 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 $$\\mathbf x = \\varphi_\\text{WGS84}^{-1}(\\phi,\\theta,d)\n",
    "  = \\left(\n",
    "    \\begin{array}{c}\n",
    "     (\\bar R(\\theta)+d) \\cos\\phi\\cos\\theta, \\\\\n",
    "     (\\bar R(\\theta)+d) \\sin\\phi\\cos\\theta, \\\\\n",
    "     ((1-e^2)\\bar R(\\theta)+d) \\sin\\theta\n",
    "    \\end{array}\n",
    "    \\right),$$ 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).\n",
    "\n",
    "2. 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 $$(\\phi,\\theta,d) = \\varphi_\\text{topo}^{-1}(\\hat\\phi,\\hat\\theta,\\hat d)\n",
    "  = \\left(\n",
    "      \\hat\\phi,\n",
    "      \\hat\\theta,\n",
    "      \\hat d + \\frac{\\hat d+500000}{500000}h(\\hat\\phi,\\hat\\theta)\n",
    "    \\right).$$ 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."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Implementation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are a number of issues we need to address in the program. At the largest scale,\n",
    "we need to write a class that implements the interface of [FunctionManifold](https://www.dealii.org/current/doxygen/deal.II/classFunctionManifold.html) by using the corresponding Python wrapper. This involves\n",
    "a function <code>push_forward()</code> that takes a point\n",
    "in the reference domain $\\hat U$ and transform it into real space using the function\n",
    "$\\varphi^{-1}$ outlined above, and its inverse function <code>pull_back()</code>\n",
    "implementing $\\varphi$. \n",
    "\n",
    "The transformations we need have two parts: the WGS 84 transformations and the topography\n",
    "transformation. Consequently, we will implement functions\n",
    "<code>push_forward_wgs84()</code> and\n",
    "<code>push_forward_topo()</code> that implement these two pieces, and\n",
    "corresponding pull back functions.\n",
    "\n",
    "The WGS 84 transformation functions are not particularly interesting (even though the\n",
    "formulas they implement are impressive). The more interesting part is the topography\n",
    "transformation. Recall that for this, we needed to evaluate the elevation function\n",
    "$h(\\hat\\phi,\\hat\\theta)$. There is of course no formula for this: Earth is what it is,\n",
    "the best one can do is look up the altitude from some table. This is, in fact what we\n",
    "will do.\n",
    "\n",
    "The data we use was originally created by the  <a\n",
    "href=\"http://en.wikipedia.org/wiki/Shuttle_Radar_Topography_Mission\">Shuttle\n",
    "Radar Topography Mission</a>, was downloaded from the US Geologic Survey\n",
    "(USGS) and processed by D. Sarah Stamps who also wrote the initial version of\n",
    "the WGS 84 transformation functions. The topography data so processed is\n",
    "stored in a file <code>topography.txt.gz</code> that, when unpacked\n",
    "looks like this:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "6.983333 25.000000 700<br>\n",
    "6.983333 25.016667 692<br>\n",
    "6.983333 25.033333 701<br>\n",
    "6.983333 25.050000 695<br>\n",
    "6.983333 25.066667 710<br>\n",
    "6.983333 25.083333 702<br>\n",
    "...<br>\n",
    "-11.983333 35.950000 707<br>\n",
    "-11.983333 35.966667 687<br>\n",
    "-11.983333 35.983333 659<br>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The data is formatted as <code>latitude longitude elevation</code> where the first two\n",
    "columns are provided in degrees North of the equator and degrees East of the Greenwich\n",
    "meridian. The final column is given in meters above the WGS 84 zero elevation.\n",
    "\n",
    "In the transformation functions, we need to evaluate $h(\\hat\\phi,\\hat\\theta)$ for a given\n",
    "longitude $\\hat\\phi$ and latitude $\\hat\\theta$. In general, this data point will not be\n",
    "available and we will have to interpolate between adjacent data points. Fortunately, \n",
    "widely available scipy library has the functionality for this. \n",
    "\n",
    "Having discussed the general outline of how we want to implement things, let us go\n",
    "to the program and show how it is done in practice."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The commented program"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Describing topography"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, we read in our topography data from the file:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[  6.983333  25.       700.      ]\n",
      " [  6.983333  25.05     695.      ]\n",
      " [  6.983333  25.1      697.      ]\n",
      " ...\n",
      " [-11.966667  35.85     722.      ]\n",
      " [-11.966667  35.9      738.      ]\n",
      " [-11.966667  35.95     693.      ]]\n"
     ]
    }
   ],
   "source": [
    "topo_data = np.loadtxt('topography.txt.gz')\n",
    "print(topo_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, create a 2-D interpolation object that would allow us to retrieve a topography value at an arbitrary location within the area. Since we have data on a regular grid with the known size, it is most efficient to use gridded 2-D interpolation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.interpolate import RegularGridInterpolator\n",
    "\n",
    "lon = np.linspace(start = np.min(topo_data[:,1]), stop = np.max(topo_data[:,1]), num = 220)\n",
    "lat = np.linspace(start = np.min(topo_data[:,0]), stop = np.max(topo_data[:,0]), num = 380)\n",
    "altitude = np.reshape(topo_data[:,2], (380, 220))\n",
    "\n",
    "topo_function = RegularGridInterpolator((lat, lon), altitude)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us also plot the data to make sure it looks right:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.colorbar.Colorbar at 0x7f5892559da0>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 110644 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.pcolormesh(lon, lat, altitude, cmap='terrain')\n",
    "plt.ylabel('Latitude')\n",
    "plt.xlabel('Longitude')\n",
    "plt.colorbar()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Describing the geometry\n",
    "\n",
    "This section describe transformation functions that we will need to construct our manifold.\n",
    "\n",
    "The following two functions then define the forward and inverse\n",
    "transformations that correspond to the WGS 84 reference shape of\n",
    "Earth. The forward transform follows the formula shown in the\n",
    "introduction. The inverse transform is significantly more complicated\n",
    "and is, at the very least, not intuitive. It also suffers from the\n",
    "fact that it returns an angle that at the end of the function we\n",
    "need to clip back into the interval $[0,2\\pi]$ if it should have\n",
    "escaped from there."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "R           = 6378137\n",
    "ellipticity = 8.1819190842622e-2\n",
    "r2d = 180. / math.pi\n",
    "d2r = math.pi / 180.\n",
    "\n",
    "def push_forward_wgs84(phi_theta_d):\n",
    "    phi   = phi_theta_d[0]\n",
    "    theta = phi_theta_d[1]\n",
    "    d     = phi_theta_d[2]\n",
    "\n",
    "    R_bar = R / math.sqrt(1. - (ellipticity**2. *\n",
    "                                math.sin(theta) * math.sin(theta)))\n",
    "\n",
    "    return [(R_bar + d) * math.cos(phi) * math.cos(theta),\n",
    "            (R_bar + d) * math.sin(phi) * math.cos(theta),\n",
    "            ((1. - ellipticity**2.) * R_bar + d) * math.sin(theta)]\n",
    "\n",
    "def pull_back_wgs84(x):\n",
    "    b   = math.sqrt(R**2 * (1 - ellipticity**2))\n",
    "    ep  = math.sqrt((R**2 - b**2) / b**2)\n",
    "    p   = math.sqrt(x[0]**2. + x[1]**2.)\n",
    "    th  = math.atan2(R * x[2], b * p)\n",
    "    phi = math.atan2(x[1], x[0])\n",
    "    theta = math.atan2(x[2] + ep**2 * b * math.pow(math.sin(th), 3.),\n",
    "                 (p -\n",
    "                  (ellipticity**2 * R * math.pow(math.cos(th), 3))))\n",
    "    R_bar = R / (math.sqrt(1 - ellipticity**2 * math.sin(theta) *\n",
    "                           math.sin(theta)))\n",
    "    R_plus_d = p / math.cos(theta)\n",
    "\n",
    "    phi_theta_d = [0, 0, 0]\n",
    "    if phi < 0:\n",
    "        phi_theta_d[0] = phi + 2 * math.pi\n",
    "    elif phi > 2 * math.pi:\n",
    "        phi_theta_d[0] = phi - 2 * math.pi\n",
    "    else:\n",
    "        phi_theta_d[0] = phi\n",
    "    phi_theta_d[1] = theta\n",
    "    phi_theta_d[2] = R_plus_d - R_bar\n",
    "    \n",
    "    return phi_theta_d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, the topography transformations follow exactly the description in the introduction. The only thing to note here is that we invoke the interpolant for the input topography data that we constructed above:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def push_forward_topo(phi_theta_d_hat):\n",
    "    d_hat = phi_theta_d_hat[2]\n",
    "    h = topo_function((phi_theta_d_hat[1]*r2d, phi_theta_d_hat[0]*r2d))\n",
    "    d = d_hat + (d_hat + 500000.) / 500000. * h\n",
    "    \n",
    "    return [phi_theta_d_hat[0], phi_theta_d_hat[1], d]\n",
    "\n",
    "def pull_back_topo(phi_theta_d):\n",
    "    d     = phi_theta_d[2]\n",
    "    h     = topo_function((phi_theta_d[1]*r2d, phi_theta_d[0]*r2d))\n",
    "    d_hat = 500000. * (d - h) / (500000. + h)\n",
    "    \n",
    "    return [phi_theta_d[0], phi_theta_d[1], d_hat]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, both of the pull back and push forward functions are just concatenations of the respective functions of the WGS 84 and topography mappings:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pull_back(space_point):\n",
    "    return pull_back_topo(pull_back_wgs84(space_point))\n",
    "\n",
    "def push_forward(chart_point):\n",
    "    return push_forward_wgs84(push_forward_topo(chart_point))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we are ready to construct a FunctionManifold object with the defined transformations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "geometry = dealii.Manifold(dim = 3, spacedim = 3)\n",
    "geometry.create_function(push_forward, pull_back)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Creating the mesh"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Having so described the properties of the geometry, not it is\n",
    "time to deal with the mesh used to discretize it. To this end,\n",
    "we create objects for the geometry and triangulation, and then\n",
    "proceed to create a $1\\times 2\\times 1$ rectangular mesh that\n",
    "corresponds to the reference domain\n",
    "$\\hat U=[26,35]\\times[-10,5]\\times[-500000,0]$. We choose\n",
    "this number of subdivisions because it leads to cells that\n",
    "are roughly like cubes instead of stretched in one direction or\n",
    "another.\n",
    "\n",
    "Of course, we are not actually interested in meshing the\n",
    "reference domain. We are interested in meshing the real domain.\n",
    "Consequently, we will use the GridTools::transform() function\n",
    "that simply moves every point of a triangulation according to\n",
    "a given transformation. The transformation function it wants is\n",
    "a function that takes as its single argument a point in the reference\n",
    "domain and returns the corresponding location in the domain that we\n",
    "want to map to. This is, of course, exactly the push forward\n",
    "function of the geometry we use. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "repetitions = [1, 2, 1]\n",
    "p1 = dealii.Point([26 * d2r, -10 * d2r, -500000])\n",
    "p2 = dealii.Point([35 * d2r, 5 * d2r, 0.])\n",
    "\n",
    "triangulation = dealii.Triangulation('3D')\n",
    "triangulation.generate_subdivided_hyper_rectangle(repetitions,\\\n",
    "                                                  p1, p2, colorize = True)\n",
    "\n",
    "triangulation.transform(push_forward)\n",
    "\n",
    "manifold_id = 0\n",
    "\n",
    "triangulation.set_manifold(number = manifold_id, manifold = geometry)\n",
    "for cell in triangulation.active_cells():\n",
    "    cell.set_all_manifold_ids(manifold_id)        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "  The last step is to refine the mesh beyond its initial $1\\times 2\\times\n",
    "  1$ coarse mesh. We could just refine globally a number of times, but\n",
    "  since for the purpose of this tutorial program we're really only\n",
    "  interested in what is happening close to the surface, we just refine 6\n",
    "  times all of the cells that have a face at a boundary with indicator 5.\n",
    "  Looking this up in the documentation of the\n",
    "  GridGenerator::subdivided_hyper_rectangle() function we have used above\n",
    "  reveals that boundary indicator 5 corresponds to the top surface of the\n",
    "  domain (and this is what the last <code>True</code> argument in the call\n",
    "  to *generate_subdivided_hyper_rectangle()* above meant: to \"color\"\n",
    "  the boundaries by assigning each boundary a unique boundary indicator)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "for n in range(6):\n",
    "    for cell in triangulation.active_cells():\n",
    "        for face in cell.faces():\n",
    "            if face.at_boundary() and face.boundary_id == 5:\n",
    "                cell.refine_flag ='isotropic'\n",
    "                \n",
    "    triangulation.execute_coarsening_and_refinement()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us now print some statistics about our mesh:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "# of cells = 19112\n",
      "smallest cell = 21579.0 m\n",
      "largest cell = 660950.0 m\n"
     ]
    }
   ],
   "source": [
    "fmt_str = '# of cells = %d\\nsmallest cell = %0.1f m\\nlargest cell = %0.1f m'\n",
    "print(fmt_str % (triangulation.n_active_cells(),\\\n",
    "                 triangulation.minimal_cell_diameter(),\\\n",
    "                 triangulation.maximal_cell_diameter()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can finally save the mesh:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "triangulation.write('mesh.vtu', 'vtu')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This gives us the following mesh (here coloured by the element volume):\n",
    "\n",
    "![Image](https://www.dealii.org/images/steps/developer/step-53.mesh.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For more details about visualization and discussion of results with underlying issues the reader is referred to the section [Results](https://www.dealii.org/current/doxygen/deal.II/step_53.html) of the original tutorial program."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
