{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5451ef11-f683-4995-bda8-c9d87abaec49",
   "metadata": {},
   "source": [
    "# Particle Dynamics with Python\n",
    "<div class=\"dateauthor\">\n",
    "07 June 2021 | Jan H. Meinke\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5822f3b3-bc03-4e2f-85f1-57cb246e3a05",
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "import random\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f7d1939b-7d73-4c0c-9d8a-d6ea39d48b49",
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib widget"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19819e70-b42c-405a-958f-70c05a972ee6",
   "metadata": {},
   "source": [
    "Particle dynamics simulations are common in various scientific fields. They are used to simulate \n",
    "the formation of galaxies and the movements of molecules in a cell. Particles can have different\n",
    "properties such as mass and charge and interact in different ways.\n",
    "\n",
    "A classical particle dynamics code solves Newton's equation of motion:\n",
    "\n",
    "$$\\mathbf F = m \\mathbf a,$$\n",
    "\n",
    "where $\\mathbf F$ is the force, $m$ the mass, and $\\mathbf a$ the acceleration. $\\mathbf F$ and \n",
    "$\\mathbf a$ are vectors.\n",
    "\n",
    "In general, this problem is only solvable analytically for two particles. If there are more \n",
    "particles, we have to look for a numerical solution.\n",
    "\n",
    "You may remember that you can calculate the velocity $\\mathbf v$ of a particle as\n",
    "\n",
    "$$\\mathbf v(t + dt) = \\mathbf v(t) + \\mathbf a(t) dt$$\n",
    "\n",
    "and the position $\\mathbf r$ as\n",
    "\n",
    "$$\\mathbf r(t + dt) = \\mathbf r(t) + \\mathbf v(t)dt + \\frac 1 2 \\mathbf a(t) dt^2.$$\n",
    "\n",
    "If we know all the positions, velocities and masses at time $t$ and can calculate the forces, we \n",
    "can follow the motion of the particles over time."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50ad1731-c5b0-4922-adc8-14e507a7b6b8",
   "metadata": {},
   "source": [
    "## Gravitational force\n",
    "Let's assume our particles only interact via gravity. Then the force between two particles is given \n",
    "by\n",
    "\n",
    "$$\\mathbf F_{ij}(t) = G\\frac{m_i m_j}{r_{ij}^2(t)} \\mathbf {\\hat r}_{ij}(t),$$\n",
    "\n",
    "where $\\mathbf F_{ij}(t)$ is the force on particle $i$ due to particle $j$. $r_{ij}(t)$ is the \n",
    "distance between particles $i$ and $j$, and $\\mathbf {\\hat r}_{ij}(t)$ is the unit vector pointing\n",
    "from $i$ to $j$.\n",
    "\n",
    "To get the total force on particle $i$, we need to sum over all $j \\neq i$:\n",
    "\n",
    "$$\\mathbf F_{i}(t) = \\sum_{j\\neq i} \\mathbf F_{ij}(t).$$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32f7c975-ed21-4c70-9168-5b7bfa5ca276",
   "metadata": {},
   "source": [
    "## The algorithm"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4288de12-8bf3-41b2-96ca-5c3c47fc0d84",
   "metadata": {},
   "source": [
    "1. Calculate the force on each particle by summing up all the forces acting on it.\n",
    "2. Integrate the equation of motion\n",
    "\n",
    "    a) Calculate the position of each particle after a time step *dt*\n",
    "    \n",
    "    b) Calculate the velocity of each particle after a time step *dt*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "539c2d60-df7b-471b-a438-d9b4efb51781",
   "metadata": {},
   "source": [
    "## (Parallel) Patterns\n",
    "In Think Vector, we got to know some patterns. Let's see how we can apply them here:\n",
    "\n",
    "(i, j) -> $\\mathbf F_{ij}$:\n",
    "    This is a map\n",
    "    \n",
    "$\\mathbf F_{ij}$ -> $\\mathbf F_{i}$:\n",
    "    This is a reduction\n",
    "    \n",
    "Calulate the new velocity:\n",
    "    This is map\n",
    "    \n",
    "Calculate the new position:\n",
    "    This is a map, too.\n",
    "    \n",
    "Now, let's try to express this in code.\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4525c8a-378a-45b7-b1e2-b67f5f07d397",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Initialize positions and velocities\n",
    "N = 50\n",
    "L2 = 5  # half the length of the box size\n",
    "epsilon = 1e-9  # softening factor\n",
    "dt = 0.1  # time step\n",
    "G = 1  # For simplicity we set the universal graviational constant to 1\n",
    "m = 1  # This corresponds to 150 x 10^9 kg\n",
    "x = [random.uniform(-L2, L2) for i in range(N)]\n",
    "y = [random.uniform(-L2, L2) for i in range(N)]\n",
    "z = [random.uniform(-L2, L2) for i in range(N)]\n",
    "vx = [0 for i in range(N)]\n",
    "vy = [0 for i in range(N)]\n",
    "vz = [0 for i in range(N)]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8fd053d2-8c88-4666-82ed-0316fe21ac34",
   "metadata": {},
   "source": [
    "### Calculating forces"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41861767-e08d-45b8-802a-28b269e3f7ee",
   "metadata": {},
   "source": [
    "To calculate the force, we need the distance vector first. These are actually 3 maps (one for each component). The result is a distance matrix. As mentioned before maps are expressed as list generators:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "338142b6-f973-4f7a-b5a4-77e76f3b758f",
   "metadata": {},
   "outputs": [],
   "source": [
    "Dxx = [(i - j) for j in x for i in x]\n",
    "Dyy = [(i - j) for j in y for i in y]\n",
    "Dzz = [(i - j) for j in z for i in z]\n",
    "D = [math.sqrt(i * i + j * j + k * k) for i, j, k in zip(Dxx, Dyy, Dzz)]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0156a2d-13ae-46dd-b3a8-cb7eb1aca0bf",
   "metadata": {},
   "source": [
    "Now that we have the vector components and the magnitude of the vector, we can calculate the forces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e841a076-504d-445b-b006-b931e3cb0bc2",
   "metadata": {},
   "outputs": [],
   "source": [
    "Fxx = [G * m * m * i / (d * d * d + epsilon) for i, d in zip(Dxx, D)]  # epsilon prevents a zero in the dominator.\n",
    "Fyy = [G * m * m * i / (d * d * d + epsilon) for i, d in zip(Dyy, D)]\n",
    "Fzz = [G * m * m * i / (d * d * d + epsilon) for i, d in zip(Dzz, D)]\n",
    "Fx = [sum(Fxx[i * N: (i + 1) * N]) for i in range(N)]\n",
    "Fy = [sum(Fyy[i * N: (i + 1) * N]) for i in range(N)]\n",
    "Fz = [sum(Fzz[i * N: (i + 1) * N]) for i in range(N)]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3de052ac-7591-4477-8285-cc15c0019a7a",
   "metadata": {},
   "source": [
    "Let's visualize the forces on the particles:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1133b4bb-111b-4aca-9326-22a7c29c8522",
   "metadata": {},
   "outputs": [],
   "source": [
    "ax = plt.figure(figsize=(6, 6)).add_subplot(projection='3d')\n",
    "ax.scatter3D(x, y, z)\n",
    "ax.quiver(x, y, z, Fx, Fy, Fz)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ccea23e5-4f4b-4ff6-b379-8d45e3fe15f4",
   "metadata": {},
   "source": [
    "### Integrating the equation of motion"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dba27f9b-350e-4e65-9f42-e3615ee30a84",
   "metadata": {},
   "source": [
    "We are ready to update the positions and velocities of our particles:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5ddc24f9-eaf3-491c-bf81-232efa584c1c",
   "metadata": {},
   "outputs": [],
   "source": [
    "x = [i + v * dt + 0.5 * f / m * dt * dt for i, v, f in zip(x, vx, Fx)]\n",
    "y = [i + v * dt + 0.5 * f / m * dt * dt for i, v, f in zip(y, vy, Fy)]\n",
    "z = [i + v * dt + 0.5 * f / m * dt * dt for i, v, f in zip(z, vz, Fz)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2266d4e8-8f67-4979-ae47-abf8508673a4",
   "metadata": {},
   "outputs": [],
   "source": [
    "vx = [v + f / m * dt for v, f in zip(vx, Fx)]\n",
    "vy = [v + f / m * dt for v, f in zip(vy, Fy)]\n",
    "vz = [v + f / m * dt for v, f in zip(vz, Fz)]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4cff076-759c-477c-9758-41bb730cd606",
   "metadata": {},
   "source": [
    "Let's take a look at the particle positions and velocities:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bf48e0d0-34f6-47ba-8a30-ba0c1e19489d",
   "metadata": {},
   "outputs": [],
   "source": [
    "ax = plt.figure(figsize=(6, 6)).add_subplot(projection='3d')\n",
    "ax.scatter3D(x, y, z)\n",
    "ax.quiver(x, y, z, vx, vy, vz)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65984f53-4b54-4f6d-aaa1-6de391150539",
   "metadata": {},
   "source": [
    "That's it. By going back to the [calculation of the forces](#Calculating-forces), we can follow the motion of the particles over time."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1f30004-a9c3-4499-84e0-976937b9f8a8",
   "metadata": {},
   "source": [
    "## Exercise\n",
    "Rewrite the program in a vectorized manner using `ndarray`s."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "039819a6-698f-43a6-a4f0-4f7b8852fbb1",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "8cb45f43-29e2-49df-a976-bf7790fe5a44",
   "metadata": {},
   "source": [
    "### Solution:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1f236119-af8c-499d-86cf-1d6b98f9e5fd",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "HPC Python 2021",
   "language": "python",
   "name": "hpcpy21"
  },
  "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.9.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}