{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "o4JZ84moBKMr"
   },
   "source": [
    "# Navier-Stokes Forward Simulation\n",
    "\n",
    "Now let's target a somewhat more complex example: a fluid simulations based on the Navier-Stokes equations. This is still very simple with Φ<sub>Flow</sub> (phiflow), as differentiable operators for all steps exist. The Navier-Stokes equations (in their incompressible form) introduce an additional pressure field $p$, and a constraint for conservation of mass, as introduced in equation {eq}`model-boussinesq2d`. We're also moving a marker field, denoted by $d$ here, with the flow. It indicates regions of higher temperature, and excerts a force via with a bouyancy factor $\\xi$:\n",
    "\n",
    "$$\\begin{aligned}\n",
    "    \\frac{\\partial \\mathbf{u}}{\\partial{t}} + \\mathbf{u} \\cdot \\nabla \\mathbf{u} &= - \\frac{\\Delta t}{\\rho} \\nabla p + \\nu \\nabla\\cdot \\nabla \\mathbf{u} + (0,1)^T \\xi d\n",
    "  \\quad \\text{s.t.} \\quad \\nabla \\cdot \\mathbf{u} = 0,\n",
    "  \\\\\n",
    "  \\frac{\\partial d}{\\partial{t}} + \\mathbf{u} \\cdot \\nabla d &= 0 \n",
    "\\end{aligned}$$\n",
    "\n",
    "\n",
    "Here $\\mathbf{g}$ collects the forcing terms. Below we'll use a simple buoyancy model. We'll solve this PDE on a closed domain with Dirchlet boundary conditions $\\mathbf{u}=0$ for the velocity, and Neumann boundaries $\\frac{\\partial p}{\\partial x}=0$ for pressure, on a domain $\\Omega$ with a physical size of $100 \\times 80$ units. \n",
    "[[run in colab]](https://colab.research.google.com/github/tum-pbs/pbdl-book/blob/main/overview-ns-forw.ipynb)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Implementation\n",
    "\n",
    "As in the previous section, the first command with a \"!\" prefix installs the [phiflow python package from GitHub](https://github.com/tum-pbs/PhiFlow) via `pip` in your python environment. (Skip or modify this command if necessary.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "da1uZcDXdVcF",
    "outputId": "1082dc87-796c-4b57-e72e-5790fc1444c9"
   },
   "outputs": [],
   "source": [
    "#!pip install --upgrade --quiet git+https://github.com/tum-pbs/PhiFlow@develop\n",
    "#!pip install --upgrade --quiet phiflow \n",
    "\n",
    "from phi.flow import *  # The Dash GUI is not supported on Google Colab, ignore the warning\n",
    "import pylab"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "BVV1IKVqDfLl"
   },
   "source": [
    "## Setting up the simulation\n",
    "\n",
    "The following code sets up a few constants, which are denoted by upper case names. We'll use $40\\times32$ cells to discretize our domain, introduce a slight viscosity via $\\nu$, and define the time step to be $\\Delta t=1.5$. \n",
    "\n",
    "We're creating a first `CenteredGrid` here, which is initialized by a `Sphere` geometry object. This will represent the inflow region `INFLOW` where hot smoke is generated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "id": "WrA3IXDxv31P"
   },
   "outputs": [],
   "source": [
    "DT = 1.5\n",
    "NU = 0.01\n",
    "\n",
    "INFLOW = CenteredGrid(Sphere(center=(30,15), radius=10), extrapolation.BOUNDARY, x=32, y=40, bounds=Box[0:80, 0:100]) * 0.2\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ExA0Pi2sFVka"
   },
   "source": [
    "The inflow will be used to inject smoke into a second centered grid `smoke` grid that represents the marker field $d$ from above. Note that we've defined a `Box` of size $100x80$ above. This is the physical scale in terms of spatial units in our simulation, i.e., a velocity of magnitude $1$ will move the smoke density by 1 unit per 1 time unit, which may be larger or smaller than a cell in the discretized grid, depending on the settings for `x,y`. You could parametrize your simulation grid to directly resemble real-world units, or keep conversion factors in mind for conversion. \n",
    "\n",
    "The inflow sphere above is already using the \"world\" coordinates: it is located at $x=30$ along the first axis, and $y=15$ (within the $100x80$ domain box).\n",
    "\n",
    "Next, we create grids for the quantities we want to simulate. For this example, we require a velocity field and a smoke density field."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "smoke = CenteredGrid(0, extrapolation.BOUNDARY, x=32, y=40, bounds=Box[0:80, 0:100])  # sampled at cell centers\n",
    "velocity = StaggeredGrid(0, extrapolation.ZERO, x=32, y=40, bounds=Box[0:80, 0:100])  # sampled in staggered form at face centers "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We sample the smoke field at the cell centers and the velocity in [staggered form](https://tum-pbs.github.io/PhiFlow/Staggered_Grids.html). The staggered grid internally contains 2 centered grids with different dimensions, and can be converted into centered grids (or simply numpy arrays) via the `unstack` function, as explained in the link above.\n",
    "\n",
    "Next we define the update step of the simulation, which calls the necessary functions to advance the state of our fluid system by `dt`. The next cell computes one such step, and plots the marker density after one simulation frame."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 282
    },
    "id": "WmGZdOwswOva",
    "outputId": "3ae4d68d-b586-4bbe-eca9-a223d7720949"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Max. velocity and mean marker density: [0.15530995, 0.008125]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7fa2f8d16eb0>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAANAAAAD4CAYAAACdW2gvAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAAsTAAALEwEAmpwYAAALCElEQVR4nO3df+hdd33H8edrMVWZQu3MQrDd7GZR8scaoZTK+ofr7Mj6TyvIsIMRWCEO7FCQQXGw6TZhwmb+GoOIXfOHqzrb0jK6HzELusFITWusaeOW1rWsIU3otNj+05n0vT/u+ZbY5fZ7v/d97zf3fr/PB1zuueece877kLw453y+h/dNVSFpOj9zqQuQlpkBkhoMkNRggKQGAyQ1vGk9d5bEIT8toxeqatvFFqxrgEa2rP8upZbzz45b4iWc1GCApAYDJDUYIKnBAEkNBkhqMEBSgwGSGgyQ1GCApAYDJDUYIKnBAEkNBkhqMEBSgwGSGgyQ1GCApAYDJDUYIKlh1QAleUuSR5J8N8kTST47zL8nyX8lOTa8ds29WmnBTNKV5xXgpqp6OclW4N+S/MOw7A+q6uvzK09abKsGqEY/3/Dy8HHr8LK/m8SE90BJtiQ5BpwFDlbVkWHR55I8nmRfkjeP+e7eJEeTHJ1NydLiyFp+HyjJ5cADwO8D/wM8D1wG7Aeerqo/WeX7ZWNFLZ/zj1bVdRdbsqZRuKp6ETgM7K6q0zXyCvA3wPXtOqUlM8ko3LbhzEOStwI3A99PsmOYF+A24Pj8ypQW0ySjcDuAA0m2MArc16rq75P8S5JtQIBjwO/Nr0xpMa3pHqi9M++BtJRmdA8k6acZIKnBAEkNBkhqMEBSgwGSGgyQ1GCApAYDJDUYIKnBAEkNBkhqMEBSgwGSGgyQ1GCApAYDJDUYIKmh09r36iRHkjyV5KtJLpt/udJimeQMtNLa91pgF7A7yQ3A54F9VfUe4EfAHXOrUlpQqwZo6P12sda+NwErfbEPMGptJW0qU7X2BZ4GXqyqc8MqzwHvGvNdW/tqw5ooQFV1vqp2AVcy6kD6vkl3UFX7q+q6cW2BpGU2bWvfDwCXJ1lpzHglcGq2pUmLb9rWvicYBekjw2p7gAfnVKO0sDqtfZ8EvpLkz4DvAF+aY53SQrK1r7QqW/tKc2GApAYDJDUYIKnBAEkNBkhqMEBSgwGSGgyQ1GCApAYDJDUYIKnBAEkNBkhqMEBSgwGSGgyQ1GCApIZJmopcleRwkieH1r6fGOZ/JsmpJMeG1y3zL1daLJM0FTkHfKqqHkvyduDRJAeHZfuq6i/mV5602FYNUFWdBk4P0y8lOcGYLqTSZrOme6Ak7wbeDxwZZt2Z5PEkdyd5x5jv2NpXG9bEba2SvA34JvC5qro/yXbgBUaN5v8U2FFVv7vKNmxrpSXUbGuVZCtwH/DlqrofoKrODD2zXwW+yKhntrSpTDIKF0ZdR09U1RcumL/jgtU+DByffXnSYptkFO5Xgd8Bvjf8xAnAp4Hbk+xidAn3DPCxOdQnLTRb+0qrsrWvNBcGSGowQFKDAZIaDJDUYICkBgMkNRggqcEASQ0GSGowQFKDAZIaDJDUYICkBgMkNRggqcEASQ0GSGrotPa9IsnBJCeH94v2hZM2sknOQCutfXcCNwAfT7ITuAs4VFXXAIeGz9KmsmqAqup0VT02TL8ErLT2vRU4MKx2ALhtTjVKC2uStlaveV1r3+1D32yA54HtY76zF9jbqFFaWBMPIgytfe8DPllVP75wWY16Y120P1ZV7a+q68a1BZKW2dStfYEzK91Jh/ez8ylRWlxTt/YFHgL2DNN7gAdnX5602FbtTJrkRuBfge8Brw6zP83oPuhrwC8AzwK/VVU/XGVbdibVEhrfmdTWvtKqbO0rzYUBkhoMkNRggKQGAyQ1GCCpwQBJDQZIajBAUoMBkhoMkNRggKQGAyQ1GCCpwQBJDQZIajBAUoMBkhomaSpyd5KzSY5fMO8zSU4lOTa8bplvmdJimuQMdA+w+yLz91XVruH18GzLkpbDJK19vwW8YbcdabPq3APdmeTx4RLPX2bQpjRtgP4a+GVgF3Aa+MtxKybZm+RokqNT7ktaWFMFqKrOVNX5qnoV+CJw/Rusa29sbVhTBWilJ/bgw8DxcetKG9mqP2+S5F7gg8A7kzwH/DHwwSS7GP0iwzPAx+ZXorS4bO0rrcrWvtJcGCCpwQBJDQZIajBAUoMBkhoMkNRggKQGAyQ1GCCpwQBJDQZIajBAUoMBkhoMkNRggKQGAyQ1GCCpwQBJDdP2xr4iycEkJ4d3GytqU5q2N/ZdwKGqugY4NHyWNp1pe2PfChwYpg8At822LGk5rNoXboztVXV6mH4e2D5uxSR7gb1T7kdaaNMG6DVVVaN+b2OX7wf2w0pfOGnjmHYU7sxKe9/h/ezsSpKWx7RnoIeAPcCfD+8PzqyiJfSTc9+Yy3a3vulDc9muZmeSYex7gX8H3pvkuSR3MArOzUlOAh8aPkubzqpnoKq6fcyiX59xLdLS8UkEqcEASQ3tYezNZl4DBmvZl4MLi8MzkNRggKQGAyQ1GCCpwUGEMdZzsGCtLlabAwuXhmcgqcEASQ0GSGowQFKDAZIaDJDUYICkBgMkNRggqcEASQ2tR3mSPAO8BJwHzlXVdbMoSloWs3gW7teq6oUZbEdaOl7CSQ3dABXwz0keHVr4/j9J9iY5muRoc1/Swulewt1YVaeS/DxwMMn3h2b0r7G1rzay1hmoqk4N72eBB4DrZ1GUtCymDlCSn03y9pVp4DeA42/8LWlj6VzCbQceSLKynb+tqn+cSVXSkpg6QFX1A+DaGdYiLR2HsaUGAyQ12JVnjHFdbhahW48deBaHZyCpwQBJDQZIajBAUoMBkhochVuji42A+Svdm5dnIKnBAEkNBkhqMEBSg4MIM+DN/ublGUhqMEBSgwGSGgyQ1NAKUJLdSf4jyVNJ7ppVUdKy6HTl2QL8FfCbwE7g9iQ7Z1WYtAw6Z6Drgaeq6gdV9b/AV4BbZ1OWtBw6AXoX8N8XfH5umPdTbO2rjWzuf0i1ta82ss4Z6BRw1QWfrxzmSZtG5wz0beCaJFczCs5Hgd9e5TsvwPlnh+l3jj5vOB7X8lnt2H5x3IJOZ9JzSe4E/gnYAtxdVU+s8p1tK9NJjm7EX7TzuJZP59ha90BV9TDwcGcb0jLzSQSp4VIGaP8l3Pc8eVzLZ+pjS5Ujy9K0vISTGgyQ1LDuAdpIT3AnuTvJ2STHL5h3RZKDSU4O7++4lDVOI8lVSQ4neTLJE0k+Mcxf6mNL8pYkjyT57nBcnx3mX53kyPB/8qtJLpt0m+saoA34BPc9wO7XzbsLOFRV1wCHhs/L5hzwqaraCdwAfHz4d1r2Y3sFuKmqrgV2AbuT3AB8HthXVe8BfgTcMekG1/sMtKGe4K6qbwE/fN3sW4EDw/QB4Lb1rGkWqup0VT02TL8EnGD0oPBSH1uNvDx83Dq8CrgJ+Powf03Htd4BmugJ7iW3vapOD9PPM/ox5qWV5N3A+4EjbIBjS7IlyTHgLHAQeBp4sarODaus6f+kgwhzVKO/ESzt3wmSvA24D/hkVf34wmXLemxVdb6qdjF6+Pl64H2d7a13gDbDE9xnkuwAGN7PXuJ6ppJkK6PwfLmq7h9mb4hjA6iqF4HDwAeAy5OsPNa2pv+T6x2g157gHkY6Pgo8tM41zNtDwJ5heg/w4CWsZSpJAnwJOFFVX7hg0VIfW5JtSS4fpt8K3Mzo/u4w8JFhtbUdV1Wt6wu4BfhPRteef7je+5/xsdwLnAZ+wuja+Q7g5xiNUJ0EvgFccanrnOK4bmR0efY4cGx43bLsxwb8CvCd4biOA380zP8l4BHgKeDvgDdPuk0f5ZEaHESQGgyQ1GCApAYDJDUYIKnBAEkNBkhq+D8TRzqZ7oeclQAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "def step(velocity, smoke, pressure, dt=1.0, buoyancy_factor=1.0):\n",
    "    smoke = advect.semi_lagrangian(smoke, velocity, dt) + INFLOW\n",
    "    buoyancy_force = smoke * (0, buoyancy_factor) >> velocity  # resamples smoke to velocity sample points\n",
    "    velocity = advect.semi_lagrangian(velocity, velocity, dt) + dt * buoyancy_force\n",
    "    velocity = diffuse.explicit(velocity, NU, dt)\n",
    "    velocity, pressure = fluid.make_incompressible(velocity)\n",
    "    return velocity, smoke, pressure\n",
    "\n",
    "velocity, smoke, pressure = step(velocity, smoke, None, dt=DT)\n",
    "\n",
    "print(\"Max. velocity and mean marker density: \" + format( [ math.max(velocity.values) , math.mean(smoke.values) ] ))\n",
    "\n",
    "pylab.imshow(np.asarray(smoke.values.numpy('y,x')), origin='lower', cmap='magma')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A lot has happened in this `step()` call: we've advected the smoke field, added an upwards force via a Boussinesq model, advected the velocity field, and finally made it divergence free via a pressure solve.\n",
    "\n",
    "The Boussinesq model uses a multiplication by a tuple `(0, buoyancy_factor)` to turn the smoke field into a 2 component force field, sampled at the staggered velocity components via the `>>` operator. **TODO, write out**\n",
    "\n",
    "The pressure projection step in `make_incompressible` is typically the computationally most expensive step in the sequence above. It solves a Poisson equation for the boundary conditions of the domain, and updates the velocity field with the gradient of the computed pressure.\n",
    "\n",
    "Just for testing, we've also printed the mean value of the velocities, and the max density after the update. As you can see in the resulting image, we have a first round region of smoke, with a slight upwards motion (which does not show here yet). \n",
    "\n",
    "## Datatypes and dimensions\n",
    "\n",
    "The variables we created for the fields of the simulation here are instances of the class `Grid`.\n",
    "Like tensors, grids also have the `shape` attribute which lists all batch, spatial and channel dimensions.\n",
    "[Shapes in phiflow](https://tum-pbs.github.io/PhiFlow/Math.html#shapes) store not only the sizes of the dimensions but also their names and types."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Smoke: (xˢ=32, yˢ=40)\n",
      "Velocity: (xˢ=32, yˢ=40, vectorᵛ=2)\n",
      "Inflow: (xˢ=32, yˢ=40), spatial only: (xˢ=32, yˢ=40)\n"
     ]
    }
   ],
   "source": [
    "print(f\"Smoke: {smoke.shape}\")\n",
    "print(f\"Velocity: {velocity.shape}\")\n",
    "print(f\"Inflow: {INFLOW.shape}, spatial only: {INFLOW.shape.spatial}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the phiflow output here indicates the type of a dimension, e.g., $^S$ for a spatial, and $^V$ for a vector dimension. Later on for learning, we'll also introduce batch dimensions.\n",
    "\n",
    "The actual content of a shape object can be obtained via `.sizes`, or alternatively we can query the size of a specific dimension `dim` via `.get_size('dim')`. Here are two examples:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape content: (32, 40, 2)\n",
      "Vector dimension: 2\n"
     ]
    }
   ],
   "source": [
    "print(f\"Shape content: {velocity.shape.sizes}\")\n",
    "print(f\"Vector dimension: {velocity.shape.get_size('vector')}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The grid values can be accessed using the `values` property. This is an important difference to a phiflow tensor object, which does not have `values`, as illustrated in the code example below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Statistics of the different simulation grids:\n",
      "(xˢ=32, yˢ=40) float32  0.0 < ... < 0.20000000298023224\n",
      "(xˢ=(31, 32), yˢ=(40, 39), vectorᵛ=2) float32  -0.12352858483791351 < ... < 0.15530994534492493\n",
      "Reordered test tensor shape: (3, 5, 2)\n"
     ]
    }
   ],
   "source": [
    "print(\"Statistics of the different simulation grids:\")\n",
    "print(smoke.values)\n",
    "print(velocity.values)\n",
    "\n",
    "# in contrast to a simple tensor:\n",
    "test_tensor = math.tensor(numpy.zeros([2, 5, 3]), spatial('x,y'), channel('vector'))\n",
    "print(\"Reordered test tensor shape: \" + format(test_tensor.numpy('vector,y,x').shape) )\n",
    "#print(test_tensor.values.numpy('y,x')) # error! tensors don't return their content via \".values\"\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Grids have many more properties which are documented [here](https://tum-pbs.github.io/PhiFlow/phi/field/#phi.field.Grid).\n",
    "Also note that the staggered grid has a [non-uniform shape](https://tum-pbs.github.io/PhiFlow/Math.html#non-uniform-tensors) because the number of faces is not equal to the number of cells (in this example the x component has $31 \\times 40$ cells, while y has $32 \\times 39$). The `INFLOW` grid naturally has the same dimensions as the `smoke` grid.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Time evolution\n",
    "\n",
    "With this setup, we can easily advance the simulation forward in time a bit more by repeatedly calling the `step` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "0hZk5HX3w4Or",
    "outputId": "f7811af7-4b58-4ff6-a8b6-6e7bedefaa6e"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Computed frame 0, max velocity 0.4609803557395935\n",
      "Computed frame 1, max velocity 0.8926814794540405\n",
      "Computed frame 2, max velocity 1.4052708148956299\n",
      "Computed frame 3, max velocity 2.036500930786133\n",
      "Computed frame 4, max velocity 2.921010971069336\n",
      "Computed frame 5, max velocity 3.828195810317993\n",
      "Computed frame 6, max velocity 4.516851425170898\n",
      "Computed frame 7, max velocity 4.861286640167236\n",
      "Computed frame 8, max velocity 5.125314235687256\n",
      "Computed frame 9, max velocity 5.476282119750977\n"
     ]
    }
   ],
   "source": [
    "for time_step in range(10):\n",
    "    velocity, smoke, pressure = step(velocity, smoke, pressure, dt=DT)\n",
    "    print('Computed frame {}, max velocity {}'.format(time_step , np.asarray(math.max(velocity.values)) ))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "GMKKWQBLHIwP"
   },
   "source": [
    "Now the hot plume is starting to rise:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 282
    },
    "id": "Mfl80CjZxZcL",
    "outputId": "92f3a9ba-d403-4799-a543-132ee8ed234c"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7fa2f902cf40>"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 5136 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "pylab.imshow(smoke.values.numpy('y,x'), origin='lower', cmap='magma')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "wnbQJvA-HPSL"
   },
   "source": [
    "Let's compute and show a few more steps of the simulation. Because of the inflow being located off-center to the left (with x position 30), the plume will curve towards the right when it hits the top wall of the domain."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 489
    },
    "id": "tkhCOzc0ITsj",
    "outputId": "f6366c12-1eb5-4ff6-e0d7-94b806bfd8e4"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Computing time step 0\n",
      "Computing time step 1\n",
      "Computing time step 2\n",
      "Computing time step 10\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 18424 characters]\n",
      "text/plain": [
       "<Figure size 1152x360 with 5 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "steps = [[ smoke.values, velocity.values.vector[0], velocity.values.vector[1] ]]\n",
    "for time_step in range(20):\n",
    "  if time_step<3 or time_step%10==0: \n",
    "    print('Computing time step %d' % time_step)\n",
    "  velocity, smoke, pressure = step(velocity, smoke, pressure, dt=DT)\n",
    "  if time_step%5==0:\n",
    "    steps.append( [smoke.values, velocity.values.vector[0], velocity.values.vector[1]] )\n",
    "\n",
    "fig, axes = pylab.subplots(1, len(steps), figsize=(16, 5))\n",
    "for i in range(len(steps)):\n",
    "    axes[i].imshow(steps[i][0].numpy('y,x'), origin='lower', cmap='magma')\n",
    "    axes[i].set_title(f\"d at t={i*5}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also take a look at the velocities. The `steps` list above already stores `vector[0]` and `vector[1]` components of the velocities as numpy arrays, which we can show next."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 31612 characters]\n",
      "text/plain": [
       "<Figure size 1152x360 with 5 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 29760 characters]\n",
      "text/plain": [
       "<Figure size 1152x360 with 5 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axes = pylab.subplots(1, len(steps), figsize=(16, 5))\n",
    "for i in range(len(steps)):\n",
    "    axes[i].imshow(steps[i][1].numpy('y,x'), origin='lower', cmap='magma')\n",
    "    axes[i].set_title(f\"u_x at t={i*5}\")\n",
    "    \n",
    "fig, axes = pylab.subplots(1, len(steps), figsize=(16, 5))\n",
    "for i in range(len(steps)):\n",
    "    axes[i].imshow(steps[i][2].numpy('y,x'), origin='lower', cmap='magma')\n",
    "    axes[i].set_title(f\"u_y at t={i*5}\")\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "ooqVxCPM8PXl"
   },
   "source": [
    "It looks simple here, but this simulation setup is a powerful tool. The simulation could easily be extended to more complex cases or 3D, and it is already fully compatible with back-propagation pipelines of deep learning frameworks. \n",
    "\n",
    "In the next chapters we'll show how to use these simulations for training NNs, and how to steer and modify them via trained NNs. This will illustrate how much we can improve the training process by having a solver in the loop, especially when the solver is _differentiable_. Before moving to these more complex training processes, we will cover a simpler supervised approach in the next chapter. This is very fundamental: even when aiming for advanced physics-based learning setups, a working supervised training is always the first step."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Next steps\n",
    "\n",
    "You could create a variety of nice fluid simulations based on this setup. E.g., try chaning the spatial resolution, the buoyancy factors, and the overall length of the simulation run."
   ]
  }
 ],
 "metadata": {
  "colab": {
   "collapsed_sections": [],
   "name": "Forw Simulations with Φ-Flow.ipynb",
   "provenance": []
  },
  "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.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
