{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "9b6b0991-8012-485f-a8a0-37e5036c97b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from abcd_graph import ABCDGraph, ABCDParams\n",
    "from abcd_graph.utils import seed\n",
    "from abcd_graph.callbacks import StatsCollector, Visualizer, PropertyCollector"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd31ad23-37d8-49a0-8853-78d0688cf436",
   "metadata": {},
   "source": [
    "# Building a graph\n",
    "\n",
    "Before building an ABCD graph, we will set our seed and initalize the various callbacks to use for data collection later. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1e450b1c-f24c-4242-b9fa-e891efa0310d",
   "metadata": {},
   "outputs": [],
   "source": [
    "seed(42)\n",
    "\n",
    "stats = StatsCollector()\n",
    "vis = Visualizer()\n",
    "props = PropertyCollector()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a2cd52b-0dd9-4ba2-b0fc-c60414fff145",
   "metadata": {},
   "source": [
    "We will now build an ABCD graph on 100 vertices using our default parameters. A concise description of the parameters can be found in Section 2.3 [here](https://math.ryerson.ca/~pralat/papers/2022_modularity-abcd.pdf). We will experiment with the parameters later in the demo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "e9e64ff7-a4cb-4ac8-b9c5-d662aff7e80a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "gamma=2.5 delta=5 zeta=0.5 beta=1.5 s=20 tau=0.8 xi=0.25\n"
     ]
    }
   ],
   "source": [
    "params = ABCDParams()\n",
    "G = ABCDGraph(params, n=100, callbacks=[stats, vis, props])\n",
    "G.build()\n",
    "print(params)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6641b711-8d59-4a7c-a947-42a3b45581cf",
   "metadata": {},
   "source": [
    "If all you need is (a) the edges and (b) the communities, then you can do the following: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "53fccabe-1837-4125-96a9-16651f2d1511",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ABCDCommunityObj(id=0, vertices=0-29), ABCDCommunityObj(id=1, vertices=30-55), ABCDCommunityObj(id=2, vertices=56-79), ABCDCommunityObj(id=3, vertices=80-99)]\n"
     ]
    }
   ],
   "source": [
    "E = G.edges\n",
    "communities = G.communities\n",
    "\n",
    "print(communities)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb4364ef-c91a-46e2-845a-4efc3ef5baf9",
   "metadata": {},
   "source": [
    "Note that the vertex labels in a community will always be a range, with the first community containing vertex 0 and the last community containing vertex n-1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "69c0cabf-904f-435c-bb63-26c412d43473",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]\n"
     ]
    }
   ],
   "source": [
    "print(communities[0].vertices)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7efde531-6b94-4b98-ad40-e4f841e03015",
   "metadata": {},
   "source": [
    "# Exporting\n",
    "\n",
    "We currently offer 4 exports."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "202afd77-d25e-4dec-bc8d-e3f117a81688",
   "metadata": {},
   "source": [
    "We can export the adjacency matrix, with an option to use a sparse matrix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "9d16b234-c7a9-4e13-ab41-6788cc186ebe",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Compressed Sparse Row sparse matrix of dtype 'bool'\n",
      "\twith 5 stored elements and shape (1, 100)>\n",
      "  Coords\tValues\n",
      "  (0, 18)\tTrue\n",
      "  (0, 21)\tTrue\n",
      "  (0, 22)\tTrue\n",
      "  (0, 62)\tTrue\n",
      "  (0, 93)\tTrue\n"
     ]
    }
   ],
   "source": [
    "M_adj = G.exporter.to_adjacency_matrix()\n",
    "M_adj_sparse = G.exporter.to_sparse_adjacency_matrix()\n",
    "print(M_adj_sparse[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed9d6f7e-cc07-4dc1-a6d3-dba2bf4e9584",
   "metadata": {},
   "source": [
    "We can also export to both igraph and networkx."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "a659166e-a817-4b8e-8fa2-48fa9ba4a6bf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'igraph.Graph'> <class 'networkx.classes.graph.Graph'>\n"
     ]
    }
   ],
   "source": [
    "G_ig = G.exporter.to_igraph()\n",
    "G_nx = G.exporter.to_networkx()\n",
    "print(type(G_ig), type(G_nx))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8fa9d058-5f65-4878-b8c5-3d430901bb39",
   "metadata": {},
   "source": [
    "# Statistics\n",
    "\n",
    "We use StatsCollector().statistics to obtain the a dictionary of statistics for our ABCD graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "64b006a5-dfe7-49c2-bb18-4b0a5626c797",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'model_used': 'configuration_model',\n",
       " 'params': ABCDParams(gamma=2.5, delta=5, zeta=0.5, beta=1.5, s=20, tau=0.8, xi=0.25),\n",
       " 'number_of_nodes': 100,\n",
       " 'start_time': datetime.datetime(2024, 9, 30, 15, 16, 56, 904087),\n",
       " 'end_time': datetime.datetime(2024, 9, 30, 15, 16, 56, 915652),\n",
       " 'time_to_build': 0.011526491998665733,\n",
       " 'number_of_edges': 321,\n",
       " 'number_of_communities': 4,\n",
       " 'expected_average_degree': 6.52002839527186,\n",
       " 'actual_average_degree': 6.42,\n",
       " 'expected_average_community_size': 27.7585580858053,\n",
       " 'actual_average_community_size': 25.0,\n",
       " 'number_of_loops': 8,\n",
       " 'number_of_multi_edges': 14,\n",
       " 'empirical_xi': 0.2461059190031153}"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "stats.statistics"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "414b387f-1dce-4106-b4b3-9c791d786c89",
   "metadata": {},
   "source": [
    "# Properties\n",
    "\n",
    "We use PropertyCollector() to obtain the various properties of our ABCD graph. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "6620a1e7-7eb5-4aa9-84b1-a1afd62025c9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The minimum degree is 5 and 43 vertices have this degree.\n",
      "The maximum degree is 11 and 1 vertices have this degree.\n"
     ]
    }
   ],
   "source": [
    "d_seq = props.degree_sequence\n",
    "d_min = min(d_seq.values())\n",
    "d_max = max(d_seq.values())\n",
    "num_min = len([i for i in d_seq if d_seq[i] == d_min])\n",
    "num_max = len([i for i in d_seq if d_seq[i] == d_max])\n",
    "print(\"The minimum degree is\",d_min,\"and\",num_min,\"vertices have this degree.\")\n",
    "print(\"The maximum degree is\",d_max,\"and\",num_max,\"vertices have this degree.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b6e24ea6-1fa9-446c-be31-a4d3acebed70",
   "metadata": {},
   "source": [
    "We can also get the xi-matrix here. In this matrix, cell (i, j) is the number of edges between community i and community j, normalized by the expected number. This is also the case when i = j, i.e., cell (i, i) is the number of edges within community i, normalized by the expected number. In theory, all diagonal cells should be very close to 1 and the remaining cells should resemble samples from a normalized binomial distribution. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "49b978d5-eba0-492d-82a7-b126bed97ef0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1.0031746  0.98987854 1.23734818 1.30608974]\n",
      " [0.98987854 1.01265823 1.24168975 0.83406433]\n",
      " [1.23734818 1.24168975 0.97652582 0.95321637]\n",
      " [1.30608974 0.83406433 0.95321637 0.96969697]]\n"
     ]
    }
   ],
   "source": [
    "M_xi = props.xi_matrix\n",
    "print(M_xi)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0ee6b4c-47b3-4136-8612-48b746584609",
   "metadata": {},
   "source": [
    "Finally, we can find the CDFs for the degree sequence and community size sequence here. We will address these in the next section."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cad0142d-aeb9-45fd-991b-bc080fc05506",
   "metadata": {},
   "source": [
    "# Visualization\n",
    "\n",
    "For n <= 100, we offer a quick drawing tool for our ABCD graph. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "02b27e7c-d799-4a8b-b642-84db0cff29b8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 242512 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "vis.draw_communities()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe228929-e6bf-4e8b-a795-54312833ee57",
   "metadata": {},
   "source": [
    "We can also draw the CDFs for both the degree sequence and the communitiy size sequence. In both cases, the actual CDFs are compared with the theoretical CDFs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "64ea46be-5da1-4e49-8ae4-34eababf67f9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 37604 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 37768 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "vis.draw_degree_cdf()\n",
    "vis.draw_community_cdf()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55d5d10b-7ffa-4f30-84fd-88b6e64840f5",
   "metadata": {},
   "source": [
    "# Modifying parameters\n",
    "\n",
    "The ABCD graph has 7 parameters, excluding the number of vertices. Let us recall them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "8a61565c-3acb-46ac-b0c6-9a198a00fd1a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "gamma=2.5 delta=5 zeta=0.5 beta=1.5 s=20 tau=0.8 xi=0.25\n"
     ]
    }
   ],
   "source": [
    "print(params)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d25b6c7-d9f8-4c6f-bf74-c529d3e51729",
   "metadata": {},
   "source": [
    "Now let us modify some of these parameters and build a new graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "e87207bb-3c98-4db7-a54e-2de991643359",
   "metadata": {},
   "outputs": [
    {
     "ename": "ValidationError",
     "evalue": "2 validation errors for ABCDParams\ngamma\n  Value error, gamma must be between 2 and 3 [type=value_error, input_value=3.1, input_type=float]\n    For further information visit https://errors.pydantic.dev/2.7/v/value_error\nbeta\n  Value error, beta must be between 1 and 2 [type=value_error, input_value=2.1, input_type=float]\n    For further information visit https://errors.pydantic.dev/2.7/v/value_error",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValidationError\u001b[0m                           Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[14], line 4\u001b[0m\n\u001b[1;32m      1\u001b[0m gamma \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m3.1\u001b[39m\n\u001b[1;32m      2\u001b[0m beta \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m2.1\u001b[39m\n\u001b[0;32m----> 4\u001b[0m params \u001b[38;5;241m=\u001b[39m \u001b[43mABCDParams\u001b[49m\u001b[43m(\u001b[49m\u001b[43mgamma\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mgamma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbeta\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mbeta\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m      5\u001b[0m G \u001b[38;5;241m=\u001b[39m Graph(params, n\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m100\u001b[39m, callbacks\u001b[38;5;241m=\u001b[39m[stats, vis, props])\n",
      "File \u001b[0;32m~/anaconda3/envs/abcd-graph-1/lib/python3.10/site-packages/pydantic/main.py:176\u001b[0m, in \u001b[0;36mBaseModel.__init__\u001b[0;34m(self, **data)\u001b[0m\n\u001b[1;32m    174\u001b[0m \u001b[38;5;66;03m# `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks\u001b[39;00m\n\u001b[1;32m    175\u001b[0m __tracebackhide__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[0;32m--> 176\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__pydantic_validator__\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvalidate_python\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mself_instance\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "\u001b[0;31mValidationError\u001b[0m: 2 validation errors for ABCDParams\ngamma\n  Value error, gamma must be between 2 and 3 [type=value_error, input_value=3.1, input_type=float]\n    For further information visit https://errors.pydantic.dev/2.7/v/value_error\nbeta\n  Value error, beta must be between 1 and 2 [type=value_error, input_value=2.1, input_type=float]\n    For further information visit https://errors.pydantic.dev/2.7/v/value_error"
     ]
    }
   ],
   "source": [
    "gamma = 3.1\n",
    "beta = 2.1\n",
    "\n",
    "params = ABCDParams(gamma=gamma, beta=beta)\n",
    "G = Graph(params, n=100, callbacks=[stats, vis, props])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5ad0a2f-1deb-4750-b57a-bbb091c93692",
   "metadata": {},
   "source": [
    "Here, an error was raised since the parameters of the ABCD model each come with some restrictions. In this case, we were not allowed to choose gamma outside of 2 and 3, nor beta outside of 1 and 2. \n",
    "\n",
    "Let us try again with vaild parameters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e3ea2546-6849-4a2a-822f-4c688554e56c",
   "metadata": {},
   "outputs": [],
   "source": [
    "gamma = 3\n",
    "beta = 2\n",
    "params = ABCDParams(gamma=gamma, beta=beta)\n",
    "G = Graph(params=params, n=100, callbacks=[stats]).build()\n",
    "stats.statistics"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7a164a4-8104-4db8-84e3-d18ef4b40c4d",
   "metadata": {},
   "source": [
    "The speed of the ABCD construction process is highly dependent on the number of loops and multi-edges generated, since each of these collisions needs to be rewired. As a rule of thumb, smaller gamma and larger delta imply a longer build time. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2d22d003-c86b-4109-8bbe-277cc1c894c2",
   "metadata": {},
   "outputs": [],
   "source": [
    "g_list = [2.1, 2.5, 2.9]\n",
    "for gamma in g_list:\n",
    "    params = ABCDParams(gamma=gamma)\n",
    "    G = Graph(params=params, n=100_000, callbacks=[stats]).build()\n",
    "    print('gamma = ',gamma)\n",
    "    print('number of collisions = ',stats.statistics['number_of_loops']+stats.statistics['number_of_multi_edges'])\n",
    "    print('time_to_build = ',stats.statistics['time_to_build'],'\\n')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1542e8a7-cd3b-44f9-b5b5-9f33732727de",
   "metadata": {},
   "outputs": [],
   "source": [
    "d_list = [2, 5, 8]\n",
    "for delta in d_list:\n",
    "    params = ABCDParams(delta=delta)\n",
    "    G = Graph(params=params, n=100_000, callbacks=[stats]).build()\n",
    "    print('delta = ',delta)\n",
    "    print('number of collisions = ',stats.statistics['number_of_loops']+stats.statistics['number_of_multi_edges'])\n",
    "    print('time_to_build = ',stats.statistics['time_to_build'],'\\n')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
