{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7fb27b941602401d91542211134fc71a",
   "metadata": {
    "colab_type": "text",
    "id": "view-in-github"
   },
   "source": [
    "# **Single-GPU Particle Mesh Simulation with JAXPM**\n",
    "\n",
    "In this notebook, we'll run a simple Particle Mesh (PM) simulation on a single GPU using `JAXPM`. This example provides a hands-on introduction to simulating the evolution of matter in the universe through basic PM techniques, allowing you to explore how cosmological structures form over time.\n",
    "\n",
    "<a href=\"https://colab.research.google.com/github/DifferentiableUniverseInitiative/JaxPM/blob/main/notebooks/01-Introduction.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9Jy5BL1XiK1s",
   "metadata": {
    "id": "9Jy5BL1XiK1s"
   },
   "source": [
    "!pip install --quiet git+https://github.com/DifferentiableUniverseInitiative/JaxPM.git"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "af3a08c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "jax.config.update(\"jax_enable_x64\", True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "c5f42bbe",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "c5f42bbe",
    "outputId": "a7841b28-5f20-4856-bd1d-f8a3572095b5"
   },
   "outputs": [],
   "source": [
    "import jax\n",
    "import jax.numpy as jnp\n",
    "import jax_cosmo as jc\n",
    "\n",
    "from jax.experimental.ode import odeint\n",
    "\n",
    "from jaxpm.painting import cic_paint , cic_paint_dx\n",
    "from jaxpm.pm import linear_field, lpt, make_ode_fn\n",
    "from jaxpm.distributed import uniform_particles"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "adc38ef4",
   "metadata": {},
   "source": [
    "### **Particle Mesh Simulation Setup**\n",
    "\n",
    "In this example, we initialize particles with uniform positions across the grid. This setup implicitly means that the Cloud-in-Cell (CIC) painting scheme will map absolute particle positions onto the grid.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "281b4d3b",
   "metadata": {
    "id": "281b4d3b"
   },
   "outputs": [],
   "source": [
    "mesh_shape = [128, 128, 128]\n",
    "box_size = [128., 128., 128.]\n",
    "snapshots = jnp.array([0.1, 0.5, 1.0])\n",
    "\n",
    "@jax.jit\n",
    "def run_simulation(omega_c, sigma8):\n",
    "    # Create a small function to generate the matter power spectrum\n",
    "    k = jnp.logspace(-4, 1, 128)\n",
    "    pk = jc.power.linear_matter_power(jc.Planck15(Omega_c=omega_c, sigma8=sigma8), k)\n",
    "    pk_fn = lambda x: jnp.interp(x.reshape([-1]), k, pk).reshape(x.shape)\n",
    "\n",
    "    # Create initial conditions\n",
    "    initial_conditions = linear_field(mesh_shape, box_size, pk_fn, seed=jax.random.PRNGKey(0))\n",
    "\n",
    "    particles = uniform_particles(mesh_shape)\n",
    "    # Create particles\n",
    "    cosmo = jc.Planck15(Omega_c=omega_c, sigma8=sigma8)\n",
    "    \n",
    "    # Initial displacement\n",
    "    dx, p, f = lpt(cosmo, initial_conditions, particles, a=0.1)\n",
    "    \n",
    "    # Evolve the simulation forward\n",
    "    res = odeint(make_ode_fn(mesh_shape), [particles + dx, p], snapshots, cosmo, rtol=1e-8, atol=1e-8)\n",
    "    \n",
    "    # Return the simulation volume at requested \n",
    "\n",
    "    return initial_conditions ,  particles + dx , res[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "826be667",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "826be667",
    "outputId": "dc43b5c4-a004-41bf-f2c8-128c17cf4de1"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/wassim/micromamba/envs/jax/lib/python3.10/site-packages/jax/_src/numpy/array_methods.py:118: UserWarning: Explicitly requested dtype <class 'jax.numpy.int64'> requested in astype is not available, and will be truncated to dtype int32. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/jax-ml/jax#current-gotchas for more.\n",
      "  return lax_numpy.astype(self, dtype, copy=copy, device=device)\n",
      "/home/wassim/micromamba/envs/jax/lib/python3.10/site-packages/jax/_src/numpy/array_methods.py:118: UserWarning: Explicitly requested dtype <class 'jax.numpy.int64'> requested in astype is not available, and will be truncated to dtype int32. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/jax-ml/jax#current-gotchas for more.\n",
      "  return lax_numpy.astype(self, dtype, copy=copy, device=device)\n",
      "/home/wassim/micromamba/envs/jax/lib/python3.10/site-packages/jax/_src/numpy/array_methods.py:118: UserWarning: Explicitly requested dtype <class 'jax.numpy.int64'> requested in astype is not available, and will be truncated to dtype int32. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/jax-ml/jax#current-gotchas for more.\n",
      "  return lax_numpy.astype(self, dtype, copy=copy, device=device)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pm_forces\n",
      "pm_forces\n"
     ]
    }
   ],
   "source": [
    "initial_conditions , lpt_particles , ode_particles = run_simulation(0.25, 0.8)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "f9c05d34",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 354776 characters]",
      "text/plain": [
       "<Figure size 2000x500 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from jaxpm.plotting import plot_fields_single_projection\n",
    "\n",
    "fields = {\"Initial Conditions\" : initial_conditions , \"LPT Field\" : cic_paint(jnp.zeros(mesh_shape) ,lpt_particles)}\n",
    "for i , field in enumerate(ode_particles[1:]):\n",
    "    fields[f\"field_{i}\"] = cic_paint(jnp.zeros(mesh_shape) , field)\n",
    "plot_fields_single_projection(fields)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd1e9196",
   "metadata": {},
   "source": [
    "# **Particle Mesh Simulation Setup - Relative Position Painting**\n",
    "\n",
    "In the second example, we leave the initial particle positions as `None`, which applies a relative position (displacement-only) CIC painting scheme. This approach assumes uniform particle positions by default, saving memory and improving efficiency, though with the trade-off of assuming uniformity.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b71824ed",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/wassim/micromamba/envs/jax/lib/python3.10/site-packages/jax/_src/numpy/array_methods.py:118: UserWarning: Explicitly requested dtype <class 'jax.numpy.int64'> requested in astype is not available, and will be truncated to dtype int32. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/jax-ml/jax#current-gotchas for more.\n",
      "  return lax_numpy.astype(self, dtype, copy=copy, device=device)\n",
      "/home/wassim/micromamba/envs/jax/lib/python3.10/site-packages/jax/_src/numpy/array_methods.py:118: UserWarning: Explicitly requested dtype <class 'jax.numpy.int64'> requested in astype is not available, and will be truncated to dtype int32. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/jax-ml/jax#current-gotchas for more.\n",
      "  return lax_numpy.astype(self, dtype, copy=copy, device=device)\n",
      "/home/wassim/micromamba/envs/jax/lib/python3.10/site-packages/jax/_src/numpy/array_methods.py:118: UserWarning: Explicitly requested dtype <class 'jax.numpy.int64'> requested in astype is not available, and will be truncated to dtype int32. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/jax-ml/jax#current-gotchas for more.\n",
      "  return lax_numpy.astype(self, dtype, copy=copy, device=device)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pm_forces\n",
      "pm_forces\n"
     ]
    }
   ],
   "source": [
    "mesh_shape = [128, 128, 128]\n",
    "box_size = [128., 128., 128.]\n",
    "snapshots = jnp.array([0.1, 0.5, 1.0])\n",
    "\n",
    "@jax.jit\n",
    "def run_simulation(omega_c, sigma8):\n",
    "    # Create a small function to generate the matter power spectrum\n",
    "    k = jnp.logspace(-4, 1, 128)\n",
    "    pk = jc.power.linear_matter_power(jc.Planck15(Omega_c=omega_c, sigma8=sigma8), k)\n",
    "    pk_fn = lambda x: jnp.interp(x.reshape([-1]), k, pk).reshape(x.shape)\n",
    "\n",
    "    # Create initial conditions\n",
    "    initial_conditions = linear_field(mesh_shape, box_size, pk_fn, seed=jax.random.PRNGKey(0))\n",
    "\n",
    "    # Create particles\n",
    "    cosmo = jc.Planck15(Omega_c=omega_c, sigma8=sigma8)\n",
    "    \n",
    "    # Initial displacement\n",
    "    dx, p, f = lpt(cosmo, initial_conditions, a=0.1)\n",
    "    \n",
    "    # Evolve the simulation forward\n",
    "    res = odeint(make_ode_fn(mesh_shape,paint_absolute_pos=False), [dx, p], snapshots, cosmo, rtol=1e-8, atol=1e-8)\n",
    "    \n",
    "    # Return the simulation volume at requested \n",
    "\n",
    "    return initial_conditions ,  dx , res[0]\n",
    "    \n",
    "initial_conditions , lpt_displacements , ode_displacements = run_simulation(0.25, 0.8)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "33b5e684",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 354772 characters]",
      "text/plain": [
       "<Figure size 2000x500 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from jaxpm.plotting import plot_fields_single_projection\n",
    "\n",
    "fields = {\"Initial Conditions\" : initial_conditions , \"LPT Field\" : cic_paint_dx(lpt_displacements)}\n",
    "for i , field in enumerate(ode_displacements[1:]):\n",
    "    fields[f\"field_{i}\"] = cic_paint_dx(field)\n",
    "plot_fields_single_projection(fields)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02f18b52",
   "metadata": {},
   "source": [
    "We note that **painting only displacements is slower** than painting absolute particle positions.\n",
    "\n",
    "This slower performance occurs because **painting displacements requires processing in smaller chunks or batches**. Instead of creating a large array of particle positions with neighbors (e.g., `(NParticles, 3, 8)`), which consumes a significant amount of memory, we paint the particles in manageable batches.\n",
    "\n",
    "This trade-off allows for greater memory efficiency but comes at the expense of speed. By reducing the memory footprint, we avoid memory limitations, especially useful in large-scale or distributed PM simulations, even if it means slightly slower painting.\n",
    "\n",
    "We’ll see in later notebooks that retaining only the displacement is essential for distributed Particle Mesh (PM) simulations, where memory efficiency and computational speed are key.\n"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "include_colab_link": true,
   "name": "Introduction.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.10.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
