{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tutorial"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is a brief tutorial of basic Metagraph usage.\n",
    "\n",
    "First, we import Metagraph:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import metagraph as mg"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Inspecting Types and Available Algorithms\n",
    "\n",
    "The default resolver automatically pulls in all registered Metagraph plugins.\n",
    "\n",
    "The default resolver also links itself with the `metagraph` namespace, allowing\n",
    "users to ignore the default resolver in most cases."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "res = mg.resolver"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A hierarchy of available types is automatically added as properties on `res`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['BipartiteGraph',\n",
       " 'DataFrame',\n",
       " 'EdgeMap',\n",
       " 'EdgeSet',\n",
       " 'Graph',\n",
       " 'Matrix',\n",
       " 'NodeMap',\n",
       " 'NodeSet',\n",
       " 'Vector']"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dir(res.types)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Alternatively, simply access the types directly from `mg`.\n",
    "\n",
    "Most attributes of `res` are exposed as attributes on `mg` for convenience."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['BipartiteGraph',\n",
       " 'DataFrame',\n",
       " 'EdgeMap',\n",
       " 'EdgeSet',\n",
       " 'Graph',\n",
       " 'Matrix',\n",
       " 'NodeMap',\n",
       " 'NodeSet',\n",
       " 'Vector']"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dir(mg.types)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Two important concepts in Metagraph are abstract types and concrete types. \n",
    "\n",
    "Abstract types describe a generic kind of data container with potentially many equivalent representations.\n",
    "\n",
    "Concrete types describe a specific data object which fits under the abstract type category.\n",
    "\n",
    "One can think of abstract types as data container specifications and concrete types as implementations of those specifications.\n",
    "\n",
    "For each abstract type, there are several concrete types.\n",
    "\n",
    "Within a single abstract type, all concrete types are able to represent equivalent data, but in a different format or data structure.\n",
    "\n",
    "Here we show the concrete types which represent `Graphs`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['GrblasGraphType', 'NetworkXGraphType', 'ScipyGraphType']"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dir(mg.types.Graph)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Available algorithms are listed under `mg.algos` and grouped by categories.\n",
    "\n",
    "Note that these are also available under `res.algos`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['bipartite',\n",
       " 'centrality',\n",
       " 'clustering',\n",
       " 'embedding',\n",
       " 'flow',\n",
       " 'subgraph',\n",
       " 'traversal',\n",
       " 'util']"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dir(mg.algos)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['all_pairs_shortest_paths',\n",
       " 'astar_search',\n",
       " 'bellman_ford',\n",
       " 'bfs_iter',\n",
       " 'bfs_tree',\n",
       " 'dfs_iter',\n",
       " 'dfs_tree',\n",
       " 'dijkstra',\n",
       " 'minimum_spanning_tree']"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dir(mg.algos.traversal)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example Usage\n",
    "\n",
    "Let's see how to use Metagraph by first constructing a graph from an edge list.\n",
    "\n",
    "Begin with an input csv file representing an edge list and weights."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = \"\"\"\n",
    "Source,Destination,Weight\n",
    "0,1,4\n",
    "0,3,2\n",
    "0,4,7\n",
    "1,3,3\n",
    "1,4,5\n",
    "2,4,5\n",
    "2,5,2\n",
    "2,6,8\n",
    "3,4,1\n",
    "4,7,4\n",
    "5,6,4\n",
    "5,7,6\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Read in the csv file and convert to a Pandas `DataFrame`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import io\n",
    "csv_file = io.StringIO(data)\n",
    "df = pd.read_csv(csv_file)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This `DataFrame` represents a graph’s edges, but Metagraph doesn’t know that yet. To use the `DataFrame` within Metagraph, we first need to convert it into an `EdgeMap`.\n",
    "\n",
    "A `PandasEdgeMap` takes a `DataFrame` plus the labels of the columns representing source, destination, and weight. With these, Metagraph will know how to interpret the `DataFrame` as an `EdgeMap`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Source</th>\n",
       "      <th>Destination</th>\n",
       "      <th>Weight</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0</td>\n",
       "      <td>4</td>\n",
       "      <td>7</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1</td>\n",
       "      <td>3</td>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>1</td>\n",
       "      <td>4</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>2</td>\n",
       "      <td>4</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>2</td>\n",
       "      <td>5</td>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>2</td>\n",
       "      <td>6</td>\n",
       "      <td>8</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>3</td>\n",
       "      <td>4</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>4</td>\n",
       "      <td>7</td>\n",
       "      <td>4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>5</td>\n",
       "      <td>6</td>\n",
       "      <td>4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11</th>\n",
       "      <td>5</td>\n",
       "      <td>7</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    Source  Destination  Weight\n",
       "0        0            1       4\n",
       "1        0            3       2\n",
       "2        0            4       7\n",
       "3        1            3       3\n",
       "4        1            4       5\n",
       "5        2            4       5\n",
       "6        2            5       2\n",
       "7        2            6       8\n",
       "8        3            4       1\n",
       "9        4            7       4\n",
       "10       5            6       4\n",
       "11       5            7       6"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "em = mg.wrappers.EdgeMap.PandasEdgeMap(df, 'Source', 'Destination', 'Weight', is_directed=False)\n",
    "em.value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Convert EdgeMap to a Graph\n",
    "\n",
    "`Graphs` and `EdgeMaps` have many similarities, but `Graphs` are more powerful. `Graphs` can have weights on the nodes, not just on the edges. `Graphs` can also have isolate nodes (nodes with no edges), which `EdgeMaps` cannot have.\n",
    "\n",
    "Most Metagraph algorithms take a `Graph` as input, so we will convert our `PandasEdgeMap` into a `Graph`. In this case, it will become a `NetworkXGraph`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<metagraph.plugins.networkx.types.NetworkXGraph at 0x7fcd30f17850>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "g = mg.algos.util.graph.build(em)\n",
    "g"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "EdgeDataView([(0, 1, {'weight': 4}), (0, 3, {'weight': 2}), (0, 4, {'weight': 7}), (1, 3, {'weight': 3}), (1, 4, {'weight': 5}), (3, 4, {'weight': 1}), (4, 2, {'weight': 5}), (4, 7, {'weight': 4}), (2, 5, {'weight': 2}), (2, 6, {'weight': 8}), (5, 6, {'weight': 4}), (5, 7, {'weight': 6})])"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "g.value.edges(data=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Translate to other Graph formats\n",
    "\n",
    "Because Metagraph knows how to interpret `g` as a `Graph`, we can easily convert it other `Graph` formats.\n",
    "\n",
    "Let's convert it to a `ScipyGraph`. This format stores the edges and weights in a scipy.sparse matrix along with a numpy array mapping the position to a NodeId (in case the nodes are not sequential from 0..n). Any node weights are stored in a separate numpy array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<metagraph.plugins.scipy.types.ScipyGraph at 0x7fcd30f2c750>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "g2 = mg.translate(g, mg.wrappers.Graph.ScipyGraph)\n",
    "g2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The matrix is accessed using `g2.value`. The node list is accessed using `.node_list`.\n",
    "\n",
    "We can verify the weighs and edges by inspecting the sparse adjacency matrix directly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 4, 0, 2, 7, 0, 0, 0],\n",
       "       [4, 0, 0, 3, 5, 0, 0, 0],\n",
       "       [0, 0, 0, 0, 5, 2, 8, 0],\n",
       "       [2, 3, 0, 0, 1, 0, 0, 0],\n",
       "       [7, 5, 5, 1, 0, 0, 0, 4],\n",
       "       [0, 0, 2, 0, 0, 0, 4, 6],\n",
       "       [0, 0, 8, 0, 0, 4, 0, 0],\n",
       "       [0, 0, 0, 0, 4, 6, 0, 0]], dtype=int64)"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "g2.value.toarray()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also convert `g` into an adjacency matrix representation using a `GrblasGraph`. This also stores the edges and node weights separately."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<metagraph.plugins.graphblas.types.GrblasGraph at 0x7fcd30f40650>"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "g3 = mg.translate(g, mg.types.Graph.GrblasGraphType)\n",
    "g3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style>\n",
       "table.gb-info-table {\n",
       "    border: 1px solid black;\n",
       "    max-width: 100%;\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 0px;\n",
       "}\n",
       "\n",
       "td.gb-info-name-cell {\n",
       "    line-height: 100%;\n",
       "}\n",
       "\n",
       "details.gb-arg-details {\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 5px;\n",
       "    margin-left: 10px;\n",
       "}\n",
       "\n",
       "summary.gb-arg-summary {\n",
       "    display: list-item;\n",
       "    outline: none;\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 0px;\n",
       "    margin-left: -10px;\n",
       "}\n",
       "\n",
       "details.gb-expr-details {\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 5px;\n",
       "}\n",
       "\n",
       "summary.gb-expr-summary {\n",
       "    display: list-item;\n",
       "    outline: none;\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 0px;\n",
       "}\n",
       "\n",
       "blockquote.gb-expr-blockquote {\n",
       "    margin-top: 5px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 0px;\n",
       "    margin-left: 15px;\n",
       "}\n",
       "\n",
       ".gb-scalar {\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 5px;\n",
       "}\n",
       "\n",
       "/* modify pandas dataframe */\n",
       "table.dataframe {\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 0px;\n",
       "}\n",
       "</style>\n",
       "<details open class=\"gb-arg-details\"><summary class=\"gb-arg-summary\"><tt>M<sub>0</sub></tt><div>\n",
       "<table class=\"gb-info-table\">\n",
       "  <tr>\n",
       "    <td rowspan=\"2\" class=\"gb-info-name-cell\"><pre>grblas.Matrix</pre></td>\n",
       "    <td><pre>nvals</pre></td>\n",
       "    <td><pre>nrows</pre></td>\n",
       "    <td><pre>ncols</pre></td>\n",
       "    <td><pre>dtype</pre></td>\n",
       "  </tr>\n",
       "  <tr>\n",
       "    <td>24</td>\n",
       "    <td>8</td>\n",
       "    <td>8</td>\n",
       "    <td>INT64</td>\n",
       "  </tr>\n",
       "</table>\n",
       "</div>\n",
       "</summary><div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>0</th>\n",
       "      <th>1</th>\n",
       "      <th>2</th>\n",
       "      <th>3</th>\n",
       "      <th>4</th>\n",
       "      <th>5</th>\n",
       "      <th>6</th>\n",
       "      <th>7</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td></td>\n",
       "      <td>4</td>\n",
       "      <td></td>\n",
       "      <td>2</td>\n",
       "      <td>7</td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>4</td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td>3</td>\n",
       "      <td>5</td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td>5</td>\n",
       "      <td>2</td>\n",
       "      <td>8</td>\n",
       "      <td></td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>2</td>\n",
       "      <td>3</td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td>1</td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>7</td>\n",
       "      <td>5</td>\n",
       "      <td>5</td>\n",
       "      <td>1</td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td>4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td>2</td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td>4</td>\n",
       "      <td>6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td>8</td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td>4</td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td>4</td>\n",
       "      <td>6</td>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div></details></div>"
      ],
      "text/plain": [
       "\"M_0\"          nvals  nrows  ncols  dtype\n",
       "grblas.Matrix     24      8      8  INT64\n",
       "-----------------------------------------\n",
       "   0  1  2  3  4  5  6  7\n",
       "0     4     2  7         \n",
       "1  4        3  5         \n",
       "2              5  2  8   \n",
       "3  2  3        1         \n",
       "4  7  5  5  1           4\n",
       "5        2           4  6\n",
       "6        8        4      \n",
       "7              4  6      "
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "g3.value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also visualize the graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import grblas\n",
    "grblas.io.draw(g3.value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Inspect the steps required for translations\n",
    "\n",
    "Rather than actually converting `g` into other formats, let’s ask Metagraph how it will do the conversion. Each conversion requires a translator (written by plugin developers) to convert between the two formats. However, even if there isn’t a direct translator between two formats, Metagraph will find a path and take several translation steps as needed to perform the task.\n",
    "\n",
    "The mechanism for viewing the plan is to invoke the translation from ``mg.plan.translate`` rather than ``mg.translate``. Other than the additional ``.plan``, the call signature is identical.\n",
    "\n",
    "In this first example, there is a direct function which translates between `NetworkXGraphType` and `ScipyGraphType`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Direct Translation]\n",
       "NetworkXGraphType -> ScipyGraphType"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mg.plan.translate(g, mg.types.Graph.ScipyGraphType)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "In this next example, there is no direct function which convert `NetworkXGraphType` into a `GrblasGraphType`. Instead, we have to first convert to `ScipyGraphType` and then to `GrblasGraphType` before finally arriving at our desired format.\n",
    "\n",
    "While Metagraph will do the conversion automatically, understanding the steps involved helps users plan for expected computation time and memory usage. If needed, plugin developers can write a plugin to provide a direct translation path. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Multi-step Translation]\n",
       "(start)  NetworkXGraphType\n",
       "           -> ScipyGraphType\n",
       " (end)       -> GrblasGraphType"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mg.plan.translate(g, mg.types.Graph.GrblasGraphType)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Algorithm Example #1: Breadth First Search\n",
    "\n",
    "Algorithms are described initially in an abstract definition. For bfs_iter, we take a `Graph` and return a `Vector` indicating the NodeIDs in the order visited.\n",
    "\n",
    "After the abstract definition is written, multiple concrete implementations are written to operate on concrete types.\n",
    "\n",
    "Let's look at the signature and specific implementations available for bfs_iter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Signature:\n",
      "\t(graph: Graph({}), source_node: NodeID, depth_limit: int = -1) -> Vector({})\n",
      "Implementations:\n",
      "\t(graph: ScipyGraphType({}), source_node: NodeID, depth_limit: int = -1) -> NumpyVectorType({})\n",
      "\t(graph: NetworkXGraphType({}), source_node: NodeID, depth_limit: int = -1) -> NumpyVectorType({})\n"
     ]
    }
   ],
   "source": [
    "mg.algos.traversal.bfs_iter.signatures"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We see that there are two implementations available, each with a different type of input graph.\n",
    "\n",
    "---\n",
    "Let's perform a breadth-first search with our different representations of `g`. We should get approximately the same answer no matter which implementation is chosen (same NodeIDs within each depth level of the traversal)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 3, 4, 2, 7, 5, 6])"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cc = mg.algos.traversal.bfs_iter(g, 0)\n",
    "cc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 3, 4, 2, 7, 5, 6])"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cc2 = mg.algos.traversal.bfs_iter(g2, 0)\n",
    "cc2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "Similar to how we can view the plan for translations, we can view the plan for algorithms.\n",
    "\n",
    "No translation is needed because we already have a concrete implementation which takes a `NetworkXGraph` as input."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "nx_breadth_first_search_iter\n",
       "(graph: NetworkXGraphType({}), source_node: NodeID, depth_limit: int = -1) -> NumpyVectorType({})\n",
       "=====================\n",
       "Argument Translations\n",
       "---------------------\n",
       "** graph **\n",
       "NetworkXGraphType\n",
       "** source_node **\n",
       "NodeID\n",
       "** depth_limit **\n",
       "int\n",
       "---------------------"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mg.plan.algos.traversal.bfs_iter(g, 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "In the next example, `g2` also satisfies a concrete implementation, so no input translation is required."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ss_breadth_first_search_iter\n",
       "(graph: ScipyGraphType({}), source_node: NodeID, depth_limit: int = -1) -> NumpyVectorType({})\n",
       "=====================\n",
       "Argument Translations\n",
       "---------------------\n",
       "** graph **\n",
       "ScipyGraphType\n",
       "** source_node **\n",
       "NodeID\n",
       "** depth_limit **\n",
       "int\n",
       "---------------------"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mg.plan.algos.traversal.bfs_iter(g2, 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Algorithm Example #2: Pagerank\n",
    "\n",
    "Let's look at the same pieces of information, but for pagerank. Pagerank takes a `Graph` and returns a `NodeMap` indicating the rank value of each node in the graph.\n",
    "\n",
    "First, let's verify the signature and the implementations available.\n",
    "\n",
    "We see that there are two implementations available, taking a `NetworkXGraph` or `GrblasGraph` as input."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Signature:\n",
      "\t(graph: Graph({'edge_type': 'map', 'edge_dtype': ('float', 'int')}), damping: float = 0.85, maxiter: int = 50, tolerance: float = 1e-05) -> NodeMap({})\n",
      "Implementations:\n",
      "\t(graph: NetworkXGraphType({}), damping: float = 0.85, maxiter: int = 50, tolerance: float = 1e-05) -> PythonNodeMapType({})\n",
      "\t(graph: GrblasGraphType({}), damping: float = 0.85, maxiter: int = 50, tolerance: float = 1e-05) -> GrblasNodeMapType({})\n"
     ]
    }
   ],
   "source": [
    "mg.algos.centrality.pagerank.signatures"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "Let's look at the steps required in the plan if we start with a `ScipyGraph`. Then let's perform the computation.\n",
    "\n",
    "We see that the `ScipyGraph` will need to be translated to a `GrblasGraph` in order to call the algorithm. **Metagraph will do this for us automatically.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "grblas_pagerank\n",
       "(graph: GrblasGraphType({}), damping: float = 0.85, maxiter: int = 50, tolerance: float = 1e-05) -> GrblasNodeMapType({})\n",
       "=====================\n",
       "Argument Translations\n",
       "---------------------\n",
       "** graph **  [Direct Translation]\n",
       "ScipyGraphType -> GrblasGraphType\n",
       "** damping **\n",
       "float\n",
       "** maxiter **\n",
       "int\n",
       "** tolerance **\n",
       "float\n",
       "---------------------"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mg.plan.algos.centrality.pagerank(g2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<metagraph.plugins.graphblas.types.GrblasNodeMap at 0x7fcd32c1cd90>"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pr = mg.algos.centrality.pagerank(g2)\n",
    "pr"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The result is a `GrblasNodeMap` which can be inspected by looking at the underlying `.value`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style>\n",
       "table.gb-info-table {\n",
       "    border: 1px solid black;\n",
       "    max-width: 100%;\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 0px;\n",
       "}\n",
       "\n",
       "td.gb-info-name-cell {\n",
       "    line-height: 100%;\n",
       "}\n",
       "\n",
       "details.gb-arg-details {\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 5px;\n",
       "    margin-left: 10px;\n",
       "}\n",
       "\n",
       "summary.gb-arg-summary {\n",
       "    display: list-item;\n",
       "    outline: none;\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 0px;\n",
       "    margin-left: -10px;\n",
       "}\n",
       "\n",
       "details.gb-expr-details {\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 5px;\n",
       "}\n",
       "\n",
       "summary.gb-expr-summary {\n",
       "    display: list-item;\n",
       "    outline: none;\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 0px;\n",
       "}\n",
       "\n",
       "blockquote.gb-expr-blockquote {\n",
       "    margin-top: 5px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 0px;\n",
       "    margin-left: 15px;\n",
       "}\n",
       "\n",
       ".gb-scalar {\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 5px;\n",
       "}\n",
       "\n",
       "/* modify pandas dataframe */\n",
       "table.dataframe {\n",
       "    margin-top: 0px;\n",
       "    margin-bottom: 0px;\n",
       "    padding-top: 0px;\n",
       "    padding-bottom: 0px;\n",
       "}\n",
       "</style>\n",
       "<details open class=\"gb-arg-details\"><summary class=\"gb-arg-summary\"><tt>v<sub>4</sub></tt><div>\n",
       "<table class=\"gb-info-table\">\n",
       "  <tr>\n",
       "    <td rowspan=\"2\" class=\"gb-info-name-cell\"><pre>grblas.Vector</pre></td>\n",
       "    <td><pre>nvals</pre></td>\n",
       "    <td><pre>size</pre></td>\n",
       "    <td><pre>dtype</pre></td>\n",
       "  </tr>\n",
       "  <tr>\n",
       "    <td>8</td>\n",
       "    <td>8</td>\n",
       "    <td>FP64</td>\n",
       "  </tr>\n",
       "</table>\n",
       "</div>\n",
       "</summary><div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>0</th>\n",
       "      <th>1</th>\n",
       "      <th>2</th>\n",
       "      <th>3</th>\n",
       "      <th>4</th>\n",
       "      <th>5</th>\n",
       "      <th>6</th>\n",
       "      <th>7</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th></th>\n",
       "      <td>0.11991</td>\n",
       "      <td>0.11991</td>\n",
       "      <td>0.129191</td>\n",
       "      <td>0.11991</td>\n",
       "      <td>0.195384</td>\n",
       "      <td>0.133008</td>\n",
       "      <td>0.093041</td>\n",
       "      <td>0.089646</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div></details></div>"
      ],
      "text/plain": [
       "\"v_4\"          nvals  size  dtype\n",
       "grblas.Vector      8     8   FP64\n",
       "---------------------------------\n",
       "        0        1         2        3         4         5         6         7\n",
       "  0.11991  0.11991  0.129191  0.11991  0.195384  0.133008  0.093041  0.089646"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pr.value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's translate it to a numpy array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.11990989, 0.11990989, 0.12919109, 0.11990989, 0.19538403,\n",
       "       0.13300793, 0.09304149, 0.08964579])"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pr_array = mg.translate(pr, mg.types.NodeMap.NumpyNodeMapType)\n",
    "pr_array.value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "**Helpful tip**: The translation type can also be specified as a string"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<metagraph.plugins.numpy.types.NumpyNodeMap at 0x7fcd30ea4490>"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mg.translate(pr, \"NumpyNodeMap\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's verify that we get the same answer with the NetworkX implementation of Pagerank. \n",
    "We can ensure the NetworkX implementation is called by passing in a NetworkXGraph. Because no translations\n",
    "are required, it will choose that implementation.\n",
    "\n",
    "The result is a `PythonNodeMapType`, which is simply a Python `dict`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{0: 0.11990989117844908,\n",
       " 1: 0.11990989117844908,\n",
       " 3: 0.11990989117844908,\n",
       " 4: 0.1953840289789895,\n",
       " 2: 0.12919108800740858,\n",
       " 5: 0.13300793197881575,\n",
       " 6: 0.09304148578762082,\n",
       " 7: 0.08964579171181795}"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pr2 = mg.algos.centrality.pagerank(g)\n",
    "pr2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Translate to a numpy array and verify the same results (within tolerance)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.11990989, 0.11990989, 0.12919109, 0.11990989, 0.19538403,\n",
       "       0.13300793, 0.09304149, 0.08964579])"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pr2_array = mg.translate(pr2, \"NumpyNodeMap\")\n",
    "pr2_array.value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ True,  True,  True,  True,  True,  True,  True,  True])"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "abs(pr2_array - pr_array.value) < 1e-15"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.7"
  },
  "nbsphinx": {
   "execute": "never"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
