{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "2133c064",
   "metadata": {},
   "source": [
    "<img src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/VU_logo.png/800px-VU_logo.png?20161029201021\" width=\"350\">\n",
    "\n",
    "### Programming large-scale parallel systems\n",
    "\n",
    "\n",
    "# All pairs of shortest paths\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7b64d5a",
   "metadata": {},
   "source": [
    "## Contents\n",
    "\n",
    "In this notebook, we will learn\n",
    "\n",
    "- How to parallelize Floyd's algorithm\n",
    "- How communication can affect the correctness of a distributed algorithm\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "394d4b7a",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-info\">\n",
    "<b>Note:</b> Do not forget to run the next cell before starting studying this notebook. \n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1dc78750",
   "metadata": {},
   "outputs": [],
   "source": [
    "using Printf\n",
    "\n",
    "function answer_checker(answer,solution)\n",
    "    if answer == solution\n",
    "        \"🥳 Well done!\"\n",
    "    else\n",
    "        \"It's not correct. Keep trying! 💪\"\n",
    "    end |> println\n",
    "end\n",
    "floyd_check(answer) = answer_checker(answer,\"c\")\n",
    "floyd_impl_check(answer) = answer_checker(answer, \"d\")\n",
    "function q1_answer(bool)\n",
    "    bool || return\n",
    "    msg = \"\"\"\n",
    "    The we can change the loop order over i and j without changing the result. Rememebr:\n",
    "    \n",
    "    C[i,j] = min(C[i,j],C[i,k]+C[k,j])\n",
    "    \n",
    "    if we substitute j=k, we get\n",
    "    \n",
    "    C[i,k] = min(C[i,k],C[i,k]+C[k,k]).\n",
    "    \n",
    "    Since C[k,k]=0, thus, C[i,k] = min(C[i,k],C[i,k]), and C[i,k] = C[i,k].\n",
    "    \n",
    "    In other words, the value of C[i,k] will not be updated at iteration k.\n",
    "    \n",
    "    The same is true for i=k.\n",
    "    \"\"\"\n",
    "    println(msg)\n",
    "end\n",
    "println(\"🥳 Well done!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24b5c21a",
   "metadata": {},
   "source": [
    "## The All Pairs of Shortest Paths (ASP) problem\n",
    "\n",
    "In this notebook, we will parallelize the [Floyd–Warshall algorithm](https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm) used to find a solution of the  all pairs of shortest paths (ASP) problem.\n",
    "\n",
    "### Problem statement\n",
    "\n",
    "- Given a graph $G$ with a distance table $C$\n",
    "- Compute the length of the shortest path between any two nodes in \n",
    "$G$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1faddbfa",
   "metadata": {},
   "source": [
    "We represent the distance table as a matrix $C$, where $C_{ij}$ is the distance from node $i$ to node $j$ via a direct connection (a single hop in the graph). If there is no direct connection from $i$ to $j$, this is represented using a large value in $C_{ij}$ representing infinity. \n",
    "The next figure shows a simple directed graph with 4 nodes an its corresponding distance matrix (labeled as \"input\")."
   ]
  },
  {
   "attachments": {
    "g32490.png": {
     "image/png": "[encoded data, 77632 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "39db374f",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 77632 characters]\"  align=\"left\" width=\"450\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ade31d26",
   "metadata": {},
   "source": [
    "The ASP problem consists in computing the minimum distance between any two pair of nodes $i$ and $j$ in the graph. All these values can be also represented as a matrix (labeled as \"output\" in the figure above). For instance, the minimum distance from node 2 to node 3 is 8 as highlighted in the figure. You can understand both input and output matrices as distance tables. The key difference is that the input contains the distance using direct connections only, whereas the output contains the (minimum) distance allowing indirect connections."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9979e39",
   "metadata": {},
   "source": [
    "### Floyd's sequential algoritm\n",
    "\n",
    "The ASP problem can be solved with the  [Floyd–Warshall algorithm](https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm). A sequential implementation of this algorithm is given in the following function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4fe447c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "function floyd!(C)\n",
    "  n = size(C,1)\n",
    "  @assert size(C,2) == n\n",
    "  for k in 1:n\n",
    "    for j in 1:n\n",
    "      for i in 1:n\n",
    "        @inbounds C[i,j] = min(C[i,j],C[i,k]+C[k,j])\n",
    "      end\n",
    "    end\n",
    "  end\n",
    "  C\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b27707b9",
   "metadata": {},
   "source": [
    "You can check that this function computes the solution of the small ASP problem in the figure above by executing next cell."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "860e537c",
   "metadata": {},
   "outputs": [],
   "source": [
    "inf = 1000\n",
    "C = [\n",
    "      0 inf inf   1\n",
    "      2   0 inf   9\n",
    "    inf   3   0 inf\n",
    "    inf inf   5   0   \n",
    "]\n",
    "floyd!(C)"
   ]
  },
  {
   "attachments": {
    "g32490.png": {
     "image/png": "[encoded data, 77632 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "c0804b07",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 77632 characters]\"  align=\"left\" width=\"450\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b9eeb64d",
   "metadata": {},
   "source": [
    "### The algorithm explained\n",
    "\n",
    "The main idea of the  Floyd–Warshall algorithm is to perform as many iterations as nodes in the graph. At iteration $k$, we update the distance matrix $C$ by finding the shortest paths between each pair of nodes, allowing indirect paths via nodes from 1 to $k$. At the last iteration, it is allowed to visit all nodes and, thus, the distance table will contain the minimum possible distances, i.e. the solution of the ASP problem.\n",
    "\n",
    "This process is cleverly done with three nested loops:\n",
    "\n",
    "```julia\n",
    "n = size(C,1)\n",
    "for k in 1:n\n",
    "    for j in 1:n\n",
    "        for i in 1:n\n",
    "            C[i,j] = min(C[i,j],C[i,k]+C[k,j])\n",
    "        end\n",
    "    end\n",
    "end\n",
    "```\n",
    "\n",
    "At each outer iteration $k$, we do a loop over the distance matrix $C$. For each pair of nodes  $i$ and $j$ we compare the current distance $C_{ij}$ against the distance via node $k$, namely $C_{ik}+C_{kj}$, and update  $C_{ij}$ with the minimum. I.e., at iteration $k$ one checks if it is beneficial to visit node $k$ to reduce the distance between nodes $i$ and $j$."
   ]
  },
  {
   "attachments": {
    "g42544.png": {
     "image/png": "[encoded data, 25744 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "b132bccb",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 25744 characters]\"  align=\"left\" width=\"400\"/>\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "722e330c",
   "metadata": {},
   "source": [
    "The update of the distance matrix at each iteration is illustrated in the next figure for the small ASP presented above. We highlight in green the distances that are updated in each iteration. Note that some distances that were initially infinity (i.e., no connection) will be updated with a finite value in this process. You can understand this as adding new edges in the graph as illustrated in the figures below."
   ]
  },
  {
   "attachments": {
    "g33734.png": {
     "image/png": "[encoded data, 68252 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "deb384be",
   "metadata": {},
   "source": [
    "\n",
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 68252 characters]\"  align=\"left\" width=\"450\"/>\n",
    "</div>"
   ]
  },
  {
   "attachments": {
    "g32779.png": {
     "image/png": "[encoded data, 67828 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "4157df08",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 67828 characters]\"  align=\"left\" width=\"450\"/>\n",
    "</div>"
   ]
  },
  {
   "attachments": {
    "g33077.png": {
     "image/png": "[encoded data, 74636 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "7ff1e8df",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 74636 characters]\"  align=\"left\" width=\"450\"/>\n",
    "</div>"
   ]
  },
  {
   "attachments": {
    "g33443.png": {
     "image/png": "[encoded data, 80672 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "15095a9a",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 80672 characters]\"  align=\"left\" width=\"450\"/>\n",
    "</div>"
   ]
  },
  {
   "attachments": {
    "g38913.png": {
     "image/png": "[encoded data, 91064 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "53a05c72",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 91064 characters]\"  align=\"left\" width=\"450\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7027ac3",
   "metadata": {},
   "source": [
    "### Serial performance\n",
    "\n",
    "Before starting to parallelize this code, we want to make sure that we have an efficient sequential implementation.  In this algorithm, the order in which we traverse the entries of matrix `C` has a significant performance impact.\n",
    "\n",
    "The following function computes the same result as for the previous function `floyd!`, but the nesting of loops over i and j is changed.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "75cac17e",
   "metadata": {},
   "outputs": [],
   "source": [
    "function floyd2!(C)\n",
    "  n = size(C,1)\n",
    "  @assert size(C,2) == n\n",
    "  for k in 1:n\n",
    "    for i in 1:n\n",
    "      for j in 1:n\n",
    "        @inbounds C[i,j] = min(C[i,j],C[i,k]+C[k,j])\n",
    "      end\n",
    "    end\n",
    "  end\n",
    "  C\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "399385e8",
   "metadata": {},
   "source": [
    " Compare the performance of both implementations (run the cell several times)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "907bc8c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "n = 1000\n",
    "C = rand(n,n)\n",
    "@time floyd!(C)\n",
    "C = rand(n,n)\n",
    "@time floyd2!(C);"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad811b10",
   "metadata": {},
   "source": [
    "The performance difference is significant. Matrices in Julia are stored in memory in column-major order (like in Fortran, unlike in C and Python). It means that it is more efficient to access the data also in column-major order (like in function `floyd!`). See this section of [Julia's performance tips](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-column-major) if you are interested in further details."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c95ea88",
   "metadata": {},
   "source": [
    "## Parallelization\n",
    "\n",
    "Now, let us move to the parallelization of the method.\n",
    "\n",
    "### Where can we exploit parallelism?\n",
    "\n",
    "Recall:\n",
    "\n",
    "```julia\n",
    "n = size(C,1)\n",
    "for k in 1:n\n",
    "    for j in 1:n\n",
    "        for i in 1:n\n",
    "            C[i,j] = min(C[i,j],C[i,k]+C[k,j])\n",
    "        end\n",
    "    end\n",
    "end\n",
    "```\n",
    "\n",
    "- The outer loop cannot be parallelized since the operations at iteration $k$ require the result of iteration $k-1$.\n",
    "- The inner loops over $i$ and $j$ can be parallelized at a given iteration $k$.\n",
    "\n",
    "\n",
    "<div class=\"alert alert-block alert-success\">\n",
    "<b>Question (hard):</b>  Can we really parallelize the loops over `i` and `j` ? To compute `C[i,j]` at iteration `k`, we first need to compute `C[i,k]` and `C[k,j]`. In order words, it seems that the order of the loops over `i` and `j` cannot be arbitrary. However,  this is not really true, why?\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d05b686",
   "metadata": {},
   "outputs": [],
   "source": [
    "uncover = false\n",
    "q1_answer(true)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f26f9b5",
   "metadata": {},
   "source": [
    "### Parallelization strategy\n",
    "\n",
    "As for the matrix-matrix product and Jacobi, any of the iterations over $i$ and $j$ are independent and could be computed on a different processor. However, we need a larger grain size for performance reason. Here, we adopt the same strategy as for algorithm 3 in the matrix-matrix product:\n",
    "\n",
    "- Each process will update a subset of consecutive rows of the distance table $C$ at each iteration $k$."
   ]
  },
  {
   "attachments": {
    "fig-asp-partition.png": {
     "image/png": "[encoded data, 26880 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "9a9e8c44",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 26880 characters]\"  align=\"left\" width=\"250\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9eab6b5d",
   "metadata": {},
   "source": [
    "### Data dependencies\n",
    "\n",
    "Recall: we perform this update at iteration $k$:\n",
    "\n",
    "`C[i,j] = min(C[i,j],C[i,k]+C[k,j])`\n",
    "\n",
    "If each process updates a block of rows of matrix $C$, which data do we need for this operation?\n"
   ]
  },
  {
   "attachments": {
    "g14170.png": {
     "image/png": "[encoded data, 23300 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "5cf07baf",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 23300 characters]\"  align=\"left\" width=\"450\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db9a3294",
   "metadata": {},
   "source": [
    "At each iteration, the processor needs the input values `C[i,j]`, `C[i,k]` and `C[k,j]`. Since we split the data row-wise, the process already has values `C[i,j]` and `C[i,k]`. However, `C[k,j]` may be stored in a different process. See figure below."
   ]
  },
  {
   "attachments": {
    "g12830.png": {
     "image/png": "[encoded data, 9232 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "e17c5271",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 9232 characters]\"  align=\"left\" width=\"225\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a01872ef",
   "metadata": {},
   "source": [
    "As we iterate over columns $j$, the process needs input from all values of row $k$. Therefore, at the start of iteration $k$, the whole row $k$ needs to be communicated."
   ]
  },
  {
   "attachments": {
    "g12957.png": {
     "image/png": "[encoded data, 9192 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "3261af17",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 9192 characters]\"  align=\"left\" width=\"225\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d1de618",
   "metadata": {},
   "source": [
    "In summary, at iteration $k$ a given process will need row number $k$ which might be stored remotely in another process. The owner of row $k$ needs to *broadcast* this row to the other processes at iteration $k$."
   ]
  },
  {
   "attachments": {
    "g12703.png": {
     "image/png": "[encoded data, 23008 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "8412a275",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 23008 characters]\"  align=\"left\" width=\"450\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2cf55de3",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-success\">\n",
    "<b>Question:</b>  How much data is send from the owner of row k in each iteration in this parallel algorithm?\n",
    "</div>\n",
    "\n",
    "    a) O(N²/P)\n",
    "    b) O(N)\n",
    "    c) O(NP)\n",
    "    d) O(P)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1bf4de56",
   "metadata": {},
   "outputs": [],
   "source": [
    "answer = \"x\" # replace x with a, b, c or d\n",
    "floyd_check(answer)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eba45ea4",
   "metadata": {},
   "source": [
    "### Theoretical performance analysis"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a15bc34e",
   "metadata": {},
   "source": [
    "### Computation complexity\n",
    "\n",
    "Each process updates $N^2/P$ entries per iteration. The computation complexity per iteration is $O(N^2/P)$."
   ]
  },
  {
   "attachments": {
    "fig-asp-efficiency-comp.png": {
     "image/png": "[encoded data, 10500 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "cc2668c8",
   "metadata": {},
   "source": [
    "<div>\n",
    "    <img src=\"data:image/png;base64,[encoded data, 10500 characters]\" align=\"left\" width=\"225\"/>\n",
    "    </div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d89d2b45",
   "metadata": {},
   "source": [
    "### Communication complexity\n",
    "\n",
    "- One process broadcasts a message of length $N$ to $P-1$ processes per iteration. Thus, the **send cost** is $O(N P)$ per iteration.\n",
    "- $P-1$ processes receive one message of length $N$ per iteration. Hence, the **receive cost** is $O(N)$ per iteration at each process. "
   ]
  },
  {
   "attachments": {
    "fig-asp-efficiency-comm-2.png": {
     "image/png": "[encoded data, 108760 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "e96eda2d",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 108760 characters]\" align=\"left\" width=\"250\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6993b9d0",
   "metadata": {},
   "source": [
    "The communication over computation cost is\n",
    "\n",
    "- On the send side: $O(NP)/O(N^2/P) = O(P^2/N)$\n",
    "- On the receive side $O(N)/O(N^2/P) = O(P/N)$\n",
    "\n",
    "\n",
    "In summary, the send/computation ratio is $O(P^2/N)$ and the receive/computation ratio is $O(P/N)$.  The algorithm is potentially scalable if $P^2<<N$. Note that this is worse than for matrix-matrix multiplication, which is scalable for $P<<N$. I.e., you need a larger problem size in the current algorithm than in matrix-matrix multiplication."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "494aa965",
   "metadata": {},
   "source": [
    "## Parallel Implementation\n",
    "\n",
    "Now, we will explore the parallel implementation of Floyd's algorithm. We will use MPI."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "91a1cbd8",
   "metadata": {},
   "outputs": [],
   "source": [
    "] add MPI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6bc0122b",
   "metadata": {},
   "outputs": [],
   "source": [
    "using MPI"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c2e0010",
   "metadata": {},
   "source": [
    "We are going to implement the method in a function with the following signature\n",
    "\n",
    "```julia\n",
    "    floyd_mpi!(C,comm)\n",
    "```\n",
    "\n",
    "The signature is similar to the sequential function `floyd!(C)`, but there are some important differences\n",
    "\n",
    "- This function is going to be called on several MPI ranks, but only one rank (rank 0 in the implementation below) will receive the input matrix, the other ranks will receive an empty matrix. It would not be memory efficient if all ranks receive a copy of the input matrix as this one could be very large.\n",
    "\n",
    "- On output, only the value of C will be correct on rank 0. It will not be efficient to try to write the result on all ranks.\n",
    "\n",
    "- The parallel function takes an MPI communicator object. This allows the user to decide which communicator to use. For instance, `MPI.COMM_WORLD` directly or a duplicate of this one (which is the recommended approach)."
   ]
  },
  {
   "attachments": {
    "g3642.png": {
     "image/png": "[encoded data, 139188 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "99554811",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 139188 characters]\" align=\"left\" width=\"300\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "075ee09d",
   "metadata": {},
   "source": [
    "The implementation of the parallel function is given in the following code snipped:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "37863d46",
   "metadata": {},
   "outputs": [],
   "source": [
    "code1 = quote\n",
    "    function floyd_mpi!(C,comm)\n",
    "        myC = distribute_input(C,comm)\n",
    "        floyd_iterations!(myC,comm)\n",
    "        collect_result!(C,myC,comm)\n",
    "    end\n",
    "end;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6cdae12",
   "metadata": {},
   "source": [
    "In the following cells, we discuss the implementation of the helper functions `distribute_input`, `floyd_iterations!`, and `collect_result!`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "627378c3",
   "metadata": {},
   "source": [
    "### Distributing the input matrix\n",
    "\n",
    "Since only rank 0 receives the input matrix C, this rank needs to split it row-wise and\n",
    "send the pieces to all other ranks. This is done in the function below. We start by communicating the problem size N to all ranks (at the start only rank 0 knows the problem size). This is trivially done with an `MPI.Bcast!`. Once all ranks know the problem size, they can allocate space for their local part of `C` , called `myC` in the code. After this, rank 0 sends the pieces to all other ranks. We do it here with `MPI.Send` and `MPI.Recv!`. This can also be done with `MPI.Scatter!`, but it is more challenging since we are using a row partition and Julia stores the matrices in column major order. Note that this algorithm can also be implemented using a column partition. In this case, using `MPI.Scatter!` would probably be the best option.\n"
   ]
  },
  {
   "attachments": {
    "g5958.png": {
     "image/png": "[encoded data, 212248 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "d1ddc881",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 212248 characters]\" align=\"left\" width=\"300\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7efe441c",
   "metadata": {},
   "outputs": [],
   "source": [
    "code2 = quote\n",
    "    function distribute_input(C,comm)\n",
    "        rank = MPI.Comm_rank(comm)\n",
    "        P = MPI.Comm_size(comm)\n",
    "        # Communicate problem size\n",
    "        if rank == 0\n",
    "            N = size(C,1)\n",
    "            if mod(N,P) !=0\n",
    "                println(\"N not multplie of P\")\n",
    "                MPI.Abort(comm,-1)\n",
    "            end\n",
    "            Nref = Ref(N)\n",
    "        else\n",
    "            Nref = Ref(0)\n",
    "        end\n",
    "        MPI.Bcast!(Nref,comm;root=0)\n",
    "        N = Nref[]\n",
    "        # Distribute C row-wise\n",
    "        L = div(N,P)\n",
    "        myC = similar(C,L,N)\n",
    "        if  rank == 0\n",
    "            lb = L*rank+1\n",
    "            ub = L*(rank+1)\n",
    "            myC[:,:] = view(C,lb:ub,:)\n",
    "            for dest in 1:(P-1)\n",
    "                lb = L*dest+1\n",
    "                ub = L*(dest+1)\n",
    "                MPI.Send(view(C,lb:ub,:),comm;dest)\n",
    "            end\n",
    "        else\n",
    "            source = 0\n",
    "            MPI.Recv!(myC,comm;source)\n",
    "        end\n",
    "        return myC\n",
    "    end\n",
    "end;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72d82138",
   "metadata": {},
   "source": [
    "### Running Floyd's updates in parallel\n",
    "\n",
    "As discussed above, we need to communicate row $k$ of matrix $C$ in order to perform iteration $k$ of the algorithm. The function below is similar to the sequential function `floyd!`, but there is a key difference. At the start of iteration $k$, the owner of row $k$ sends it to the other processors. Once row $k$ is available, all ranks can do the Floyd update locally on their portion of matrix $C$."
   ]
  },
  {
   "attachments": {
    "fig-asp-efficiency-comm-2.png": {
     "image/png": "[encoded data, 108760 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "6abd8c34",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 108760 characters]\" align=\"left\" width=\"250\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52961b52",
   "metadata": {},
   "outputs": [],
   "source": [
    "code3 = quote\n",
    "    function floyd_iterations!(myC,comm)\n",
    "        L = size(myC,1)\n",
    "        N = size(myC,2)\n",
    "        rank = MPI.Comm_rank(comm)\n",
    "        P = MPI.Comm_size(comm)\n",
    "        lb = L*rank+1\n",
    "        ub = L*(rank+1)\n",
    "        C_k = similar(C,N)\n",
    "        for k in 1:N\n",
    "            if (lb<=k) && (k<=ub)\n",
    "                # Send row k to other workers if I have it\n",
    "                myk = (k-lb)+1\n",
    "                C_k[:] = view(myC,myk,:)\n",
    "                for dest in 0:(P-1)\n",
    "                    if rank == dest\n",
    "                        continue\n",
    "                    end\n",
    "                    MPI.Send(C_k,comm;dest)\n",
    "                end\n",
    "            else\n",
    "                # Wait until row k is received\n",
    "                MPI.Recv!(C_k,comm,source=MPI.ANY_SOURCE)\n",
    "            end\n",
    "            # Now, we have the data dependencies and\n",
    "            # we can do the updates locally\n",
    "            for j in 1:N\n",
    "                for i in 1:L\n",
    "                    myC[i,j] = min(myC[i,j],myC[i,k]+C_k[j])\n",
    "                end\n",
    "            end\n",
    "        end\n",
    "        myC\n",
    "    end\n",
    "end;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54081b85",
   "metadata": {},
   "source": [
    "### Collecting back the results\n",
    "\n",
    "At this point, we have solved the ASP problem, but the solution is cut in different pieces, each one stored on a different MPI rank. It is often useful to gather the solution into a single matrix, e.g., to compare it against the sequential algorithm.The following function collects all pieces and stores them in $C$ on rank 0. Again, we implement this with `MPI.Send` and `MPI.Recv!` as it is easier as we are working with a row partition. However, we could do it also with `MPI.Gather!`."
   ]
  },
  {
   "attachments": {
    "g7043.png": {
     "image/png": "[encoded data, 213044 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "a0fb6974",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 213044 characters]\" align=\"left\" width=\"300\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8c725aa9",
   "metadata": {},
   "outputs": [],
   "source": [
    "code4 = quote\n",
    "    function collect_result!(C,myC,comm)\n",
    "        L = size(myC,1)\n",
    "        rank = MPI.Comm_rank(comm)\n",
    "        P = MPI.Comm_size(comm)\n",
    "        if rank == 0\n",
    "            lb = L*rank+1\n",
    "            ub = L*(rank+1)\n",
    "            C[lb:ub,:] = myC\n",
    "            for source in 1:(P-1)\n",
    "                lb = L*source+1\n",
    "                ub = L*(source+1)\n",
    "                MPI.Recv!(view(C,lb:ub,:),comm;source)\n",
    "            end\n",
    "        else\n",
    "            dest = 0\n",
    "            MPI.Send(myC,comm;dest)\n",
    "        end\n",
    "        C\n",
    "    end\n",
    "end;"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d573108e",
   "metadata": {},
   "source": [
    "### Running and testing the code\n",
    "\n",
    "In the cell below, we run the parallel code and compare it against the sequential. Note that we can only compare both results on rank 0 since this is the only one that contains the result of the parallel code. We have also included a function that generates random distance tables of a given size $N$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c8e9cea7",
   "metadata": {},
   "outputs": [],
   "source": [
    "code = quote\n",
    "    using MPI\n",
    "    MPI.Init()\n",
    "    $code1\n",
    "    $code2\n",
    "    $code3\n",
    "    $code4\n",
    "    function input_distance_table(n)\n",
    "        threshold = 0.1\n",
    "        mincost = 3\n",
    "        maxcost = 9\n",
    "        inf = 10000\n",
    "        C = fill(inf,n,n)\n",
    "        for j in 1:n\n",
    "            for i in 1:n\n",
    "                if rand() > threshold\n",
    "                    C[i,j] = rand(mincost:maxcost)\n",
    "                end\n",
    "            end\n",
    "            C[j,j] = 0\n",
    "        end\n",
    "        C\n",
    "    end\n",
    "    function floyd!(C)\n",
    "        n = size(C,1)\n",
    "      @assert size(C,2) == n\n",
    "      for k in 1:n\n",
    "        for j in 1:n\n",
    "          for i in 1:n\n",
    "            @inbounds C[i,j] = min(C[i,j],C[i,k]+C[k,j])\n",
    "          end\n",
    "        end\n",
    "      end\n",
    "      C\n",
    "    end\n",
    "    comm = MPI.Comm_dup(MPI.COMM_WORLD)\n",
    "    rank = MPI.Comm_rank(comm)\n",
    "    if rank == 0\n",
    "        N = 24\n",
    "    else\n",
    "        N = 0\n",
    "    end\n",
    "    C = input_distance_table(N)\n",
    "    C_par = copy(C)\n",
    "    floyd_mpi!(C_par,comm)\n",
    "    if rank == 0\n",
    "        C_seq = copy(C)\n",
    "        floyd!(C_seq)\n",
    "        if C_seq == C_par\n",
    "            println(\"Test passed 🥳\")\n",
    "        else\n",
    "            println(\"Test failed\")\n",
    "        end\n",
    "    end\n",
    "end\n",
    "run(`$(mpiexec()) -np 3 julia --project=. -e $code`);"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91a772df",
   "metadata": {},
   "source": [
    "## Is this implementation correct?\n",
    "\n",
    "In the cell above, the result of the parallel code was probably the same as for the sequential code. However, is this sufficient to assert that the code is correct? Unfortunately, it is not. In fact, **the parallel code we implemented is not correct!** There is no guarantee that this code computes the correct result. This is why:\n",
    "\n",
    "In MPI, point-to-point messages are *non-overtaking* between a given sender and receiver. Say that process 1 sends several messages to process 3. All these will arrive in FIFO order. This is according to section 3.5 of the MPI standard 4.0.\n",
    "Unfortunately, this is not enough in our case. The messages could arrive in the wrong order *from different senders*. If process 1 sends messages to process 3, and then process 2 sends other messages to process 3, it is not granted that process 3 will receive first the messages from process 1 and then from process 2 (see figure below)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1277772d",
   "metadata": {},
   "source": [
    "If we are lucky all messages will arrive in order. In our parallel code, all processors would receive first row one, then row 2, then row 3, etc. The computed result will be correct."
   ]
  },
  {
   "attachments": {
    "g33659.png": {
     "image/png": "[encoded data, 15212 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "f84ef616",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 15212 characters]\"  align=\"left\" width=\"225\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df60e4e7",
   "metadata": {},
   "source": [
    "However, FIFO ordering between pairs of processors is not enough to guarantee that rows arrive in consecutive order. The next figure shows a counter example. In this case, communication between process 1 and process 3 is particularly slow for some unknown reason. As result processor 3 receives first messages from processor 2, even though processor 1 sent the messages first. In our parallel code, the received rows would be first row 3, then row 4, then row 1, then row 2, which is not correct. Note however that process 3 received all messages from process 1 in the correct order (guaranteed by MPI). But this is not enough in our algorithm."
   ]
  },
  {
   "attachments": {
    "g33721.png": {
     "image/png": "[encoded data, 18896 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "bdae65f8",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"data:image/png;base64,[encoded data, 18896 characters]\"  align=\"left\" width=\"225\"/>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86534783",
   "metadata": {},
   "source": [
    "### Possible solutions\n",
    "\n",
    "There are several solution to this synchronization problem:\n",
    "\n",
    "1. **Synchronous sends**: Use synchronous send MPI_SSEND. This is less efficient because we spend time waiting until each message is received. Note that the blocking send MPI_SEND used above does not guarantee that the message was received. \n",
    "2. **MPI.Barrier**: Use a barrier at the end of each iteration over $k$. This is easy to implement, but we get a  synchronization overhead.\n",
    "3. **Order incoming messages**: The receiver orders the incoming messages, e.g. using to MPI.Status to get the sender rank. This requires buffering and extra user code.\n",
    "4. **MPI.Bcast!**: Communicate row k using `MPI.Bcast!`. One needs to know which are the rows owned by the other ranks since we cannot use `MPI.ANY_SOURCE` in `MPI.Bcast!`. This is trivial however if the number of rows is multiple of the number of ranks."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb21edc4",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "- We learned how to parallelize Floyd's algorithm.\n",
    "- The considered strategy based on a row-wise data partition has little communication overhead if the problem size is large enough.\n",
    "- One needs to be careful in which order the messages are received to have a correct algorithm.\n",
    "- There are several strategies to solve this synchronization problem, each one with pros and cons.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de96ad1b",
   "metadata": {},
   "source": [
    "## Exercise\n",
    "\n",
    "### Exercise 1\n",
    "\n",
    "Modify the `floyd_iterations!` function so that it is guaranteed that the result is computed correctly. Use `MPI.Bcast!` to solve the synchronization problem. Note: only use `MPI.Bcast!`in `floyd_iterations!`, do not use other MPI directives. You can assume that the number of rows is a multiple of the number of processes."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c789dc7a",
   "metadata": {},
   "source": [
    "# License\n",
    "\n",
    "\n",
    "\n",
    "This notebook is part of the course [Programming Large Scale Parallel Systems](https://www.francescverdugo.com/XM_40017) at Vrije Universiteit Amsterdam and may be used under a [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) license."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Julia 1.9.0",
   "language": "julia",
   "name": "julia-1.9"
  },
  "language_info": {
   "file_extension": ".jl",
   "mimetype": "application/julia",
   "name": "julia",
   "version": "1.9.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
