From b74e22ab9f17c0f99de8ae8600f17e66cd26a6ea Mon Sep 17 00:00:00 2001 From: Ralph Liu <106174412+oorliu@users.noreply.github.com> Date: Tue, 2 Aug 2022 16:33:26 -0400 Subject: [PATCH] Use Datasets API to Update Notebook Examples (#2440) Addresses issue #2364 All of the SG notebook examples have been updated to use the newly added Datasets API. Previously, Graph objects were created by specifying a path to the `.csv` file, calling `cuDF` to read in the file, and then converting the edge list to a graph. Now, a dataset object is imported and can create graphs by calling the `get_graph()` method. Comments and headings have also been updated for continuity. Authors: - Ralph Liu (https://github.com/oorliu) Approvers: - Rick Ratzel (https://github.com/rlratzel) URL: https://github.com/rapidsai/cugraph/pull/2440 --- .../algorithms/centrality/Betweenness.ipynb | 56 +- notebooks/algorithms/centrality/Katz.ipynb | 53 +- notebooks/algorithms/community/ECG.ipynb | 37 +- notebooks/algorithms/community/Louvain.ipynb | 37 +- .../community/Spectral-Clustering.ipynb | 41 +- .../community/Subgraph-Extraction.ipynb | 53 +- .../community/Triangle-Counting.ipynb | 65 +- notebooks/algorithms/community/ktruss.ipynb | 40 +- .../components/ConnectedComponents.ipynb | 36 +- notebooks/algorithms/cores/core-number.ipynb | 39 +- notebooks/algorithms/cores/kcore.ipynb | 75 +- .../algorithms/layout/Force-Atlas2.ipynb | 31 +- notebooks/applications/CostMatrix.ipynb | 641 ------------------ notebooks/centrality/Centrality.ipynb | 386 +++++++++++ notebooks/img/Full-four_node_replication.png | Bin 17291 -> 0 bytes notebooks/img/graph_after_ghost.png | Bin 37330 -> 0 bytes notebooks/img/graph_after_replication.png | Bin 33718 -> 0 bytes notebooks/link_analysis/HITS.ipynb | 44 +- notebooks/link_analysis/Pagerank.ipynb | 43 +- .../link_prediction/Jaccard-Similarity.ipynb | 37 +- .../link_prediction/Overlap-Similarity.ipynb | 27 +- notebooks/sampling/RandomWalk.ipynb | 31 +- notebooks/structure/Renumber-2.ipynb | 36 +- notebooks/structure/Renumber.ipynb | 19 +- notebooks/structure/Symmetrize.ipynb | 27 +- notebooks/traversal/BFS.ipynb | 27 +- notebooks/traversal/SSSP.ipynb | 34 +- 27 files changed, 727 insertions(+), 1188 deletions(-) delete mode 100644 notebooks/applications/CostMatrix.ipynb create mode 100644 notebooks/centrality/Centrality.ipynb delete mode 100644 notebooks/img/Full-four_node_replication.png delete mode 100644 notebooks/img/graph_after_ghost.png delete mode 100644 notebooks/img/graph_after_replication.png diff --git a/notebooks/algorithms/centrality/Betweenness.ipynb b/notebooks/algorithms/centrality/Betweenness.ipynb index 8860819b3ad..82b7b4bc29e 100644 --- a/notebooks/algorithms/centrality/Betweenness.ipynb +++ b/notebooks/algorithms/centrality/Betweenness.ipynb @@ -12,7 +12,8 @@ "| --------------|------------|------------------|-----------------|----------------|\n", "| Brad Rees | 04/24/2019 | created | 0.15 | GV100, CUDA 11.0\n", "| Brad Rees | 08/16/2020 | tested / updated | 21.10 nightly | RTX 3090 CUDA 11.4\n", - "| Don Acosta | 07/05/2022 | tested / updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5" + "| Don Acosta | 07/05/2022 | tested / updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5\n", + "| Ralph Liu | 07/26/2022 | updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5" ] }, { @@ -111,7 +112,10 @@ "source": [ "# Import needed libraries\n", "import cugraph\n", - "import cudf" + "import cudf\n", + "\n", + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { @@ -124,42 +128,6 @@ "import networkx as nx" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Some Prep" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define the path to the test data \n", - "datafile='../../data/karate-data.csv'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Read in the data - GPU\n", - "cuGraph depends on cuDF for data loading and the initial Dataframe creation\n", - "\n", - "The data file contains an edge list, which represents the connection of a vertex to another. The `source` to `destination` pairs is in what is known as Coordinate Format (COO). In this test case, the data is just two columns. However a third, `weight`, column is also possible" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -173,9 +141,8 @@ "metadata": {}, "outputs": [], "source": [ - "# create a Graph using the source (src) and destination (dst) vertex pairs from the Dataframe \n", - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(gdf, source='src', destination='dst')" + "# Create a graph using the imported Dataset object\n", + "G = karate.get_graph(fetch=True)" ] }, { @@ -256,6 +223,7 @@ "outputs": [], "source": [ "# Read the data, this also created a NetworkX Graph \n", + "datafile=\"../../data/karate-data.csv\"\n", "file = open(datafile, 'rb')\n", "Gnx = nx.read_edgelist(file)" ] @@ -321,7 +289,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -335,11 +303,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.7" }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" } } }, diff --git a/notebooks/algorithms/centrality/Katz.ipynb b/notebooks/algorithms/centrality/Katz.ipynb index f3537fe75e7..b62cea2df82 100755 --- a/notebooks/algorithms/centrality/Katz.ipynb +++ b/notebooks/algorithms/centrality/Katz.ipynb @@ -12,7 +12,8 @@ "| --------------|------------|------------------|-----------------|----------------|\n", "| Brad Rees | 10/15/2019 | created | 0.14 | GV100, CUDA 10.2\n", "| Brad Rees | 08/16/2020 | tested / updated | 0.15.1 nightly | RTX 3090 CUDA 11.4\n", - "| Don Acosta | 07/05/2022 | tested / updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5" + "| Don Acosta | 07/05/2022 | tested / updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5\n", + "| Ralph Liu | 07/26/2022 | updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5" ] }, { @@ -40,9 +41,9 @@ " this value is 0.0f, cuGraph will use the default value which is 0.00001. \n", " Setting too small a tolerance can lead to non-convergence due to numerical \n", " roundoff. Usually values between 0.01 and 0.00001 are acceptable.\n", - " nstart:cuDataFrame, GPU Dataframe containing the initial guess for katz centrality. \n", + " nstart: cuDataFrame, GPU Dataframe containing the initial guess for katz centrality. \n", " Default is None\n", - " normalized:bool, If True normalize the resulting katz centrality values. \n", + " normalized: bool, If True normalize the resulting katz centrality values. \n", " Default is True\n", "\n", "Returns:\n", @@ -106,7 +107,10 @@ "source": [ "# Import rapids libraries\n", "import cugraph\n", - "import cudf" + "import cudf\n", + "\n", + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { @@ -140,35 +144,6 @@ "tol = 0.00001 # tolerance" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define the path to the test data \n", - "datafile='../../data/karate-data.csv'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Read in the data - GPU\n", - "cuGraph depends on cuDF for data loading and the initial Dataframe creation\n", - "\n", - "The data file contains an edge list, which represents the connection of a vertex to another. The `source` to `destination` pairs is in what is known as Coordinate Format (COO). In this test case, the data is just two columns. However a third, `weight`, column is also possible" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -182,9 +157,8 @@ "metadata": {}, "outputs": [], "source": [ - "# create a Graph using the source (src) and destination (dst) vertex pairs from the Dataframe \n", - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(gdf, source='src', destination='dst')" + "# Create a graph using the imported Dataset object\n", + "G = karate.get_graph(fetch=True)" ] }, { @@ -275,6 +249,7 @@ "outputs": [], "source": [ "# Read the data, this also created a NetworkX Graph \n", + "datafile = \"../../data/karate-data.csv\"\n", "file = open(datafile, 'rb')\n", "Gnx = nx.read_edgelist(file)" ] @@ -348,7 +323,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -362,11 +337,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.7" }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" } } }, diff --git a/notebooks/algorithms/community/ECG.ipynb b/notebooks/algorithms/community/ECG.ipynb index 28d44f5e3b2..829be21035c 100644 --- a/notebooks/algorithms/community/ECG.ipynb +++ b/notebooks/algorithms/community/ECG.ipynb @@ -13,6 +13,7 @@ "| | 08/16/2020 | updated | 0.15 | GV100, CUDA 10.2 |\n", "| | 08/05/2021 | tested/updated | 21.10 nightly | RTX 3090 CUDA 11.4 |\n", "| Don Acosta | 07/20/2022 | tested/updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |\n", + "| Ralph Liu | 07/26/2022 | updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |\n", "\n", "## Introduction\n", "\n", @@ -101,34 +102,17 @@ "source": [ "# Import needed libraries\n", "import cugraph\n", - "import cudf" + "import cudf\n", + "\n", + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Read data using cuDF" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Test file \n", - "datafile='../../data/karate-data.csv'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# read the data using cuDF\n", - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" + "## Create an Edgelist" ] }, { @@ -137,6 +121,9 @@ "metadata": {}, "outputs": [], "source": [ + "# You can also just get the edgelist\n", + "gdf = karate.get_edgelist(fetch=True)\n", + "\n", "# The algorithm also requires that there are vertex weights. Just use 1.0 \n", "gdf[\"data\"] = 1.0" ] @@ -232,7 +219,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -246,11 +233,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.7" }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" } } }, diff --git a/notebooks/algorithms/community/Louvain.ipynb b/notebooks/algorithms/community/Louvain.ipynb index 4786fb1e9dc..a8529483534 100755 --- a/notebooks/algorithms/community/Louvain.ipynb +++ b/notebooks/algorithms/community/Louvain.ipynb @@ -15,6 +15,7 @@ "| | 08/16/2020 | updated | 0.14 | GV100, CUDA 10.2 |\n", "| | 08/05/2021 | tested / updated | 21.10 nightly | RTX 3090 CUDA 11.4 |\n", "| Don Acosta | 07/11/2022 | tested / updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |\n", + "| Ralph Liu | 07/26/2022 | updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |\n", "\n", "\n", "\n", @@ -140,34 +141,17 @@ "source": [ "# Import needed libraries\n", "import cugraph\n", - "import cudf" + "import cudf\n", + "\n", + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Read data using cuDF" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Test file \n", - "datafile='../../data//karate-data.csv'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# read the data using cuDF\n", - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" + "## Create an Edgelist" ] }, { @@ -176,6 +160,9 @@ "metadata": {}, "outputs": [], "source": [ + "# You can also just get the edgelist\n", + "gdf = karate.get_edgelist(fetch=True)\n", + "\n", "# The algorithm also requires that there are vertex weights. Just use 1.0 \n", "gdf[\"data\"] = 1.0" ] @@ -323,7 +310,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -337,11 +324,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.7" }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" } } }, diff --git a/notebooks/algorithms/community/Spectral-Clustering.ipynb b/notebooks/algorithms/community/Spectral-Clustering.ipynb index a314861090c..2ac1b9e8c16 100755 --- a/notebooks/algorithms/community/Spectral-Clustering.ipynb +++ b/notebooks/algorithms/community/Spectral-Clustering.ipynb @@ -13,7 +13,8 @@ "| ---------------------------|------------|------------------|-----------------|-----------------------------|\n", "| Brad Rees and James Wyles | 08/01/2019 | created | 0.14 | GV100 32G, CUDA 10.2 |\n", "| | 08/16/2020 | updated | 0.15 | GV100 32G, CUDA 10.2 |\n", - "| Don Acosta | 07/11/2022 | tested / updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |" + "| Don Acosta | 07/11/2022 | tested / updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |\n", + "| Ralph Liu | 07/26/2022 | updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |" ] }, { @@ -140,48 +141,34 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Import needed libraries\n", "import cugraph\n", "import cudf\n", - "import numpy as np" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Read the CSV datafile using cuDF" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Test file \n", - "datafile='../../data/karate-data.csv'\n", + "import numpy as np\n", "\n", - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Add Edge Weights" + "### Create Edgelist and Add Edge Weights" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ + "gdf = karate.get_edgelist(fetch=True)\n", + "\n", "# The algorithm requires that there are edge weights. In this case all the weights are being set to 1\n", "gdf[\"data\"] = cudf.Series(np.ones(len(gdf), dtype=np.float32))" ] @@ -219,7 +206,7 @@ "metadata": {}, "outputs": [], "source": [ - "# create a CuGraph \n", + "# create a Graph \n", "G = cugraph.Graph()\n", "G.from_cudf_edgelist(gdf, source='src', destination='dst', edge_attr='data')" ] @@ -390,7 +377,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.6.9 64-bit", "language": "python", "name": "python3" }, @@ -404,11 +391,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.6.9" }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" } } }, diff --git a/notebooks/algorithms/community/Subgraph-Extraction.ipynb b/notebooks/algorithms/community/Subgraph-Extraction.ipynb index 88577d756ba..22c226fbb7a 100755 --- a/notebooks/algorithms/community/Subgraph-Extraction.ipynb +++ b/notebooks/algorithms/community/Subgraph-Extraction.ipynb @@ -13,7 +13,8 @@ "| --------------|------------|------------------|-----------------|-----------------------------|\n", "| Brad Rees | 10/16/2019 | created | 0.13 | GV100 32G, CUDA 10.2 |\n", "| | 08/16/2020 | updated | 0.15 | GV100 32G, CUDA 10.2 |\n", - "| Don Acosta | 07/11/2022 | tested / updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |" + "| Don Acosta | 07/11/2022 | tested / updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |\n", + "| Ralph Liu | 07/26/2022 | updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |" ] }, { @@ -79,7 +80,10 @@ "source": [ "# Import needed libraries\n", "import cugraph\n", - "import cudf" + "import cudf\n", + "\n", + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { @@ -89,26 +93,6 @@ "## Read data using cuDF" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Test file \n", - "datafile='../../data//karate-data.csv'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# read the data using cuDF\n", - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -123,19 +107,14 @@ "metadata": {}, "outputs": [], "source": [ + "# You can also just get the edgelist\n", + "gdf = karate.get_edgelist(fetch=True)\n", + "\n", "# The louvain algorithm requires that there are vertex weights. Just use 1.0 \n", - "gdf[\"data\"] = 1.0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create a Graph \n", - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(gdf, source='src', destination='dst', edge_attr='data')" + "gdf[\"data\"] = 1.0\n", + "\n", + "# Create a graph\n", + "G = cugraph.from_cudf_edgelist(gdf, source='src', destination='dst')" ] }, { @@ -275,7 +254,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -289,11 +268,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.7" }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" } } }, diff --git a/notebooks/algorithms/community/Triangle-Counting.ipynb b/notebooks/algorithms/community/Triangle-Counting.ipynb index 74006ae9cda..0554fac1362 100755 --- a/notebooks/algorithms/community/Triangle-Counting.ipynb +++ b/notebooks/algorithms/community/Triangle-Counting.ipynb @@ -14,6 +14,7 @@ "| Brad Rees | 08/01/2019 | created | 0.13 | GV100 32G, CUDA 10.2 |\n", "| | 08/16/2020 | updated | 0.15 | GV100 32G, CUDA 10.2 |\n", "| Don Acosta | 07/11/2022 | tested / updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |\n", + "| Ralph Liu | 07/27/2022 | updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |\n", "\n", "## Introduction\n", "Triangle Counting, as the name implies, finds the number of triangles in a graph. Triangles are important in computing the clustering Coefficient and can be used for clustering. \n", @@ -90,8 +91,11 @@ "# Import needed libraries\n", "import cugraph\n", "import cudf\n", + "from collections import OrderedDict\n", "from cugraph.experimental import triangle_count as experimental_triangles\n", - "from collections import OrderedDict" + "\n", + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { @@ -106,23 +110,6 @@ "from scipy.io import mmread" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Some Prep" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define the path to the test data \n", - "datafile='../../data/karate.csv'" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -137,6 +124,7 @@ "metadata": {}, "outputs": [], "source": [ + "datafile= '../../data/karate.csv'\n", "# Read the data, this also created a NetworkX Graph \n", "file = open(datafile, 'rb')\n", "df = pd.read_csv(\n", @@ -211,36 +199,6 @@ "# cuGraph" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Read in the data - GPU\n", - "cuGraph depends on cuDF for data loading and the initial Dataframe creation\n", - "\n", - "The data file contains an edge list, which represents the connection of a vertex to another. The `source` to `destination` pairs is in what is known as Coordinate Format (COO). A third, `weight`, column is also used in this example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Test file \n", - "gdf = cudf.read_csv(\n", - " datafile,\n", - " delimiter=\" \",\n", - " header=None,\n", - " names=[\"0\", \"1\", \"weight\"],\n", - " dtype={\"0\": \"int32\", \"1\": \"int32\", \"weight\": \"float32\"})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] - }, { "cell_type": "markdown", "metadata": {}, @@ -254,9 +212,8 @@ "metadata": {}, "outputs": [], "source": [ - "# create a Graph using the source (src) and destination (dst) vertex pairs from the Dataframe \n", - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(gdf, source=\"0\", destination=\"1\",edge_attr=\"weight\")" + "G = karate.get_graph()\n", + "G = G.to_undirected()" ] }, { @@ -330,7 +287,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.6.9 64-bit", "language": "python", "name": "python3" }, @@ -344,11 +301,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.6.9" }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" } } }, diff --git a/notebooks/algorithms/community/ktruss.ipynb b/notebooks/algorithms/community/ktruss.ipynb index 20c14d76986..3c96f7ff5a7 100644 --- a/notebooks/algorithms/community/ktruss.ipynb +++ b/notebooks/algorithms/community/ktruss.ipynb @@ -14,6 +14,7 @@ "| | 08/16/2020 | updated | 0.15 | GV100, CUDA 10.2 |\n", "| | 08/05/2021 | tested/updated | 21.10 nightly | RTX 3090 CUDA 11.4 |\n", "| Don Acosta | 07/08/2022 | tested/updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |\n", + "| Ralph Liu | 07/26/2022 | updated | 22.08 nightly | DGX Tesla V100 CUDA 11.5 |\n", "## Introduction\n", "\n", "Compute the k-truss of the graph G. A K-Truss is a relaxed cliques where every vertex is supported by at least k-2 triangle.\n", @@ -96,34 +97,17 @@ "source": [ "# Import needed libraries\n", "import cugraph\n", - "import cudf" + "import cudf\n", + "\n", + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Read data using cuDF" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Test file \n", - "datafile='../../data//karate-data.csv'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# read the data using cuDF\n", - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" + "### Create a Graph" ] }, { @@ -132,9 +116,9 @@ "metadata": {}, "outputs": [], "source": [ - "# create a Graph \n", - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(gdf, source='src', destination='dst')" + "# Create a graph using the imported Dataset object\n", + "G = karate.get_graph(fetch=True)\n", + "G = G.to_undirected()" ] }, { @@ -260,7 +244,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -274,11 +258,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.7" }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" } } }, diff --git a/notebooks/algorithms/components/ConnectedComponents.ipynb b/notebooks/algorithms/components/ConnectedComponents.ipynb index 0259c314ccf..5f18352647f 100755 --- a/notebooks/algorithms/components/ConnectedComponents.ipynb +++ b/notebooks/algorithms/components/ConnectedComponents.ipynb @@ -16,10 +16,11 @@ "\n", "_Notebook Credits_\n", "\n", - "| Author Credit | Date | Update | cuGraph Version | Test Hardware |\n", - "| --------------|------------|------------------|-----------------|-----------------------------|\n", - "| Kumar Aatish | 08/13/2019 | created | 0.15 | GV100, CUDA 10.2 |\n", - "| Brad Rees | 10/18/2021 | updated | 21.12 nightly | GV100, CUDA 11.4 |\n", + "| Author Credit | Date | Update | cuGraph Version | Test Hardware |\n", + "| --------------|------------|------------------|-----------------|--------------------|\n", + "| Kumar Aatish | 08/13/2019 | created | 0.15 | GV100, CUDA 10.2 |\n", + "| Brad Rees | 10/18/2021 | updated | 21.12 nightly | GV100, CUDA 11.4 |\n", + "| Ralph Liu | 06/22/2022 | updated/tested | 22.08 | TV100, CUDA 11.5 |\n", "| Don Acosta | 07/22/2021 | updated | 22.08 nightly | DGX Tesla V100, CUDA 11.5 |\n", "\n", "\n", @@ -131,13 +132,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 1. Read graph data from file\n", - "\n", - "cuGraph depends on cuDF for data loading and the initial Dataframe creation on the GPU.\n", - "\n", - "The data file contains an edge list, which represents the connection of a vertex to another. The source to destination pairs is in what is known as Coordinate Format (COO).\n", - "\n", - "In this test case the data in the test file is expressed in three columns, source, destination and the edge weight. While edge weight is relevant in other algorithms, cuGraph connected component calls do not make use of it and hence that column can be discarded from the dataframe." + "### 1. Import a Built-In Dataset" ] }, { @@ -146,14 +141,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Test file\n", - "datafile='../../data/netscience.csv'\n", - "\n", - "# the datafile contains three columns, but we only want to use the first two. \n", - "# We will use the \"usecols' feature of read_csv to ignore that column\n", - "\n", - "gdf = cudf.read_csv(datafile, delimiter=' ', names=['src', 'dst', 'wgt'], dtype=['int32', 'int32', 'float32'], usecols=['src', 'dst'])\n", - "gdf.head(5)" + "from cugraph.experimental.datasets import netscience" ] }, { @@ -169,9 +157,7 @@ "metadata": {}, "outputs": [], "source": [ - "# create a Graph using the source (src) and destination (dst) vertex pairs from the Dataframe\n", - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(gdf, source='src', destination='dst')" + "G = netscience.get_graph(fetch=True)" ] }, { @@ -362,7 +348,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -376,11 +362,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.9.7" }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" } } }, diff --git a/notebooks/algorithms/cores/core-number.ipynb b/notebooks/algorithms/cores/core-number.ipynb index 64b8eada7ef..06fe570d390 100755 --- a/notebooks/algorithms/cores/core-number.ipynb +++ b/notebooks/algorithms/cores/core-number.ipynb @@ -16,6 +16,7 @@ "| --------------|------------|------------------|-----------------|--------------------|\n", "| Brad Rees | 10/28/2019 | created | 0.13 | GV100, CUDA 10.2 |\n", "| Don Acosta | 07/21/2022 | updated/tested | 22.08 nightly | DGX Tesla V100, CUDA 11.5 |\n", + "| Ralph Liu | 07/26/2022 | updated/tested | 22.08 nightly | DGX Tesla V100, CUDA 11.5 |\n", "\n", "## Introduction\n", "\n", @@ -77,34 +78,17 @@ "source": [ "# Import needed libraries\n", "import cugraph\n", - "import cudf" + "import cudf\n", + "\n", + "# import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Read data using cuDF" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Test file \n", - "datafile='../../data/karate-data.csv'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# read the data using cuDF\n", - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" + "### Create a Graph" ] }, { @@ -113,9 +97,8 @@ "metadata": {}, "outputs": [], "source": [ - "# create a Graph \n", - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(gdf, source='src', destination='dst')" + "G = karate.get_graph(fetch=True)\n", + "G = G.to_undirected()" ] }, { @@ -160,7 +143,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -174,11 +157,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.9.7" }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" } } }, diff --git a/notebooks/algorithms/cores/kcore.ipynb b/notebooks/algorithms/cores/kcore.ipynb index 432a46834a1..065f02ffd98 100755 --- a/notebooks/algorithms/cores/kcore.ipynb +++ b/notebooks/algorithms/cores/kcore.ipynb @@ -17,6 +17,7 @@ "| Brad Rees | 10/28/2019 | created | 0.13 | GV100, CUDA 10.2 |\n", "| Brad Rees | 08/16/2020 | created | 0.15 | GV100, CUDA 10.2 |\n", "| Don Acosta | 07/21/2022 | updated/tested | 22.08 nightly | DGX Tesla V100, CUDA 11.5 |\n", + "| Ralph Liu | 07/26/2022 | updated/tested | 22.08 nightly | DGX Tesla V100, CUDA 11.5 |\n", "\n", "## Introduction\n", "\n", @@ -71,58 +72,50 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# Import needed libraries\n", "import cugraph\n", - "import cudf" + "import cudf\n", + "\n", + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Read data using cuDF" + "### Create a Graph" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ - "# Test file \n", - "datafile='../../data/karate-data.csv'" + "G = karate.get_graph(fetch=True)\n", + "G = G.to_undirected()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], - "source": [ - "# read the data using cuDF\n", - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create a Graph \n", - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(gdf, source='src', destination='dst')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Main Graph\n", + "\tNumber of Vertices: 34\n", + "\tNumber of Edges: 156\n" + ] + } + ], "source": [ "print(\"Main Graph\")\n", "print(\"\\tNumber of Vertices: \" + str(G.number_of_vertices()))\n", @@ -138,9 +131,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "RuntimeError", + "evalue": "non-success value returned from cugraph_core_number: CUGRAPH_UNKNOWN_ERROR", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/home/nfs/ralphl/datasets-api/notebooks/algorithms/cores/kcore.ipynb Cell 10\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[39m# Call k-cores on the graph\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m kcg \u001b[39m=\u001b[39m cugraph\u001b[39m.\u001b[39;49mk_core(G)\n", + "File \u001b[0;32m~/miniconda3/envs/cugraph_dev/lib/python3.9/site-packages/cugraph-22.2.0a0+366.gabd2f0ef-py3.9-linux-x86_64.egg/cugraph/cores/k_core.py:103\u001b[0m, in \u001b[0;36mk_core\u001b[0;34m(G, k, core_number)\u001b[0m\n\u001b[1;32m 99\u001b[0m core_number \u001b[39m=\u001b[39m G\u001b[39m.\u001b[39madd_internal_vertex_id(core_number, \u001b[39m'\u001b[39m\u001b[39mvertex\u001b[39m\u001b[39m'\u001b[39m,\n\u001b[1;32m 100\u001b[0m cols)\n\u001b[1;32m 102\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m--> 103\u001b[0m core_number \u001b[39m=\u001b[39m _call_plc_core_number(G)\n\u001b[1;32m 104\u001b[0m core_number \u001b[39m=\u001b[39m core_number\u001b[39m.\u001b[39mrename(\n\u001b[1;32m 105\u001b[0m columns\u001b[39m=\u001b[39m{\u001b[39m\"\u001b[39m\u001b[39mcore_number\u001b[39m\u001b[39m\"\u001b[39m: \u001b[39m\"\u001b[39m\u001b[39mvalues\u001b[39m\u001b[39m\"\u001b[39m}, copy\u001b[39m=\u001b[39m\u001b[39mFalse\u001b[39;00m\n\u001b[1;32m 106\u001b[0m )\n\u001b[1;32m 108\u001b[0m \u001b[39mif\u001b[39;00m k \u001b[39mis\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n", + "File \u001b[0;32m~/miniconda3/envs/cugraph_dev/lib/python3.9/site-packages/cugraph-22.2.0a0+366.gabd2f0ef-py3.9-linux-x86_64.egg/cugraph/cores/k_core.py:27\u001b[0m, in \u001b[0;36m_call_plc_core_number\u001b[0;34m(G)\u001b[0m\n\u001b[1;32m 25\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39m_call_plc_core_number\u001b[39m(G):\n\u001b[1;32m 26\u001b[0m vertex, core_number \u001b[39m=\u001b[39m \\\n\u001b[0;32m---> 27\u001b[0m pylibcugraph_core_number(\n\u001b[1;32m 28\u001b[0m resource_handle\u001b[39m=\u001b[39;49mResourceHandle(),\n\u001b[1;32m 29\u001b[0m graph\u001b[39m=\u001b[39;49mG\u001b[39m.\u001b[39;49m_plc_graph,\n\u001b[1;32m 30\u001b[0m degree_type\u001b[39m=\u001b[39;49m\u001b[39mNone\u001b[39;49;00m,\n\u001b[1;32m 31\u001b[0m do_expensive_check\u001b[39m=\u001b[39;49m\u001b[39mFalse\u001b[39;49;00m\n\u001b[1;32m 32\u001b[0m )\n\u001b[1;32m 34\u001b[0m df \u001b[39m=\u001b[39m cudf\u001b[39m.\u001b[39mDataFrame()\n\u001b[1;32m 35\u001b[0m df[\u001b[39m\"\u001b[39m\u001b[39mvertex\u001b[39m\u001b[39m\"\u001b[39m] \u001b[39m=\u001b[39m vertex\n", + "File \u001b[0;32mcore_number.pyx:124\u001b[0m, in \u001b[0;36mpylibcugraph.core_number.core_number\u001b[0;34m()\u001b[0m\n", + "File \u001b[0;32mutils.pyx:51\u001b[0m, in \u001b[0;36mpylibcugraph.utils.assert_success\u001b[0;34m()\u001b[0m\n", + "\u001b[0;31mRuntimeError\u001b[0m: non-success value returned from cugraph_core_number: CUGRAPH_UNKNOWN_ERROR" + ] + } + ], "source": [ "# Call k-cores on the graph\n", "kcg = cugraph.k_core(G) " @@ -267,7 +276,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.9.13 ('cugraph_dev')", "language": "python", "name": "python3" }, @@ -285,7 +294,7 @@ }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "8a663d26f441a9657bbd22051a1abab57e1b3709a9f7822414e6eae68c6232e8" } } }, diff --git a/notebooks/algorithms/layout/Force-Atlas2.ipynb b/notebooks/algorithms/layout/Force-Atlas2.ipynb index 90c39294cca..00fb9318790 100644 --- a/notebooks/algorithms/layout/Force-Atlas2.ipynb +++ b/notebooks/algorithms/layout/Force-Atlas2.ipynb @@ -20,6 +20,7 @@ "| -----------------|------------|------------------|-----------------|----------------|\n", "| Hugo Linsenmaier | 11/16/2020 | created | 0.17 | GV100, CUDA 11.0\n", "| Brad Rees | 01/11/2022 | tested / updated | 22.02 nightly | RTX A6000 CUDA 11.5\n", + "| Ralph Liu | 06/22/2022 | updated/tested | 22.08 | TV100, CUDA 11.5\n", " " ] }, @@ -49,7 +50,6 @@ "outputs": [], "source": [ "# Import RAPIDS libraries\n", - "\n", "import cudf\n", "import cugraph\n", "import time" @@ -94,8 +94,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Define the path to the test data \n", - "datafile = '../../data/netscience.csv'" + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import netscience" ] }, { @@ -127,18 +127,7 @@ "metadata": {}, "outputs": [], "source": [ - "edges_gdf = cudf.read_csv(datafile, names=[\"source\", \"destination\", \"weights\"],\n", - " delimiter=' ', dtype=[\"int32\", \"int32\", \"float32\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(edges_gdf, renumber=False)\n", + "G = netscience.get_graph()\n", "G.number_of_nodes(), G.number_of_edges()" ] }, @@ -187,6 +176,8 @@ "metadata": {}, "outputs": [], "source": [ + "edges_gdf = netscience.get_edgelist()\n", + "\n", "connected = calc_connected_edges(pos_gdf,\n", " edges_gdf,\n", " node_x=\"x\",\n", @@ -194,8 +185,8 @@ " node_x_dtype=\"float32\",\n", " node_y_dtype=\"float32\",\n", " node_id=\"vertex\",\n", - " edge_source=\"source\",\n", - " edge_target=\"destination\",\n", + " edge_source=\"src\",\n", + " edge_target=\"dst\",\n", " edge_aggregate_col=None,\n", " edge_render_type=\"direct\",\n", " )" @@ -234,7 +225,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.13 ('cugraph_dev')", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -248,11 +239,11 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.9.7" }, "vscode": { "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" } } }, diff --git a/notebooks/applications/CostMatrix.ipynb b/notebooks/applications/CostMatrix.ipynb deleted file mode 100644 index 687b1526069..00000000000 --- a/notebooks/applications/CostMatrix.ipynb +++ /dev/null @@ -1,641 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# How to compute a _Cost Matrix_ by replicating data\n", - "# Skip notebook test\n", - "\n", - "### Approach\n", - "A simple approach to creating a cost matrix is to run All-Source Shortest Path (ASSP), however cuGraph currently does not have an All-Source Shortest Path (ASSP) algorithm. One is on the roadmap, based on Floyd-Warshall, but that doesn't help us today. Luckily there is a work around if the graph to be processed is small. The hack is to run ASSP by creating a lot of copies of the graph and running the Single Source Shortest Path (SSSP) on one seed per graph copy. Since each SSSP run within its own disjoint component, there is no issue with path collisions between seeds. \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Notebook Organization\n", - "The first portion of the notebook discusses each step independently. It gives insight into what is going on and how fast each step takes.\n", - "\n", - "The second section puts it all the steps together in a single function and times how long with would take to compute the matrix\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Data\n", - "\n", - "In this notebook we will use the email-Eu-core\n", - "\n", - "* Number of Vertices: 1,005\n", - "* Number of Edges: 25,571\n", - "\n", - "We are using this dataset since it is small with a few communities, meaning that there are paths to be found." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Notebook Revisions\n", - "\n", - "| Author Credit | Date | Update | cuGraph Version | Test Hardware |\n", - "| --------------|------------|------------------|-----------------|----------------|\n", - "| Brad Rees | 06/21/2022 | created | 22.08 | V100 w 32 GB, CUDA 11.5\n", - "| Don Acosta | 06/28/2022 | modified | 22.08 | V100 w 32 GB, CUDA 11.5" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### References\n", - "\n", - "* https://www.sciencedirect.com/topics/mathematics/cost-matrix\n", - "* https://en.wikipedia.org/wiki/Shortest_path_problem\n", - "\n", - "Dataset\n", - "* Hao Yin, Austin R. Benson, Jure Leskovec, and David F. Gleich. Local Higher-order Graph Clustering. In Proceedings of the 23rd ACM SIGKDD International Conference on Knowledge Discovery and Data Mining. 2017.\n", - "\n", - "* J. Leskovec, J. Kleinberg and C. Faloutsos. Graph Evolution: Densification and Shrinking Diameters. ACM Transactions on Knowledge Discovery from Data (ACM TKDD), 1(1), 2007. http://www.cs.cmu.edu/~jure/pubs/powergrowth-tkdd.pdf\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# system and other\n", - "import time\n", - "from time import perf_counter\n", - "import math\n", - "\n", - "# rapids\n", - "import cugraph\n", - "import cudf" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "-----\n", - "# Reading the data\n", - "\n", - "Let's start with data read" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# simple function to read in the CSV data file\n", - "def read_data_cudf(datafile):\n", - " gdf = cudf.read_csv(datafile,\n", - " delimiter=\" \",\n", - " header=None,\n", - " names=['src','dst', 'wt'])\n", - " return gdf" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# function to determine the number of nodes in the dataset\n", - "def find_number_of_nodes(df):\n", - " node = cudf.concat([df['src'], df['dst']])\n", - " node = node.unique()\n", - " return len(node)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Read the data and verify that it is zero based (e.g. first vertex is 0)\n", - "**IMPORTANT:** The node numbering must be zero based. We use the starting index on the replicated graph to be one larger than the number of vertices. If the starting index is not zero, then the graph copies will overlap in index space and not be independent (disjoint). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "t1 = perf_counter()\n", - "gdf = read_data_cudf('../data/email-Eu-core.csv')\n", - "read_t = perf_counter() - t1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(f\" read {len(gdf)} edges in {read_t} seconds\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# verify that the starting ID is zero\n", - "min([gdf['src'].min(), gdf['dst'].min()])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# check the max ID\n", - "max([gdf['src'].max(), gdf['dst'].max()])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# the number of nodes should be one greater than the max ID\n", - "# that is the ID that we start the next instance of the data at\n", - "offset = find_number_of_nodes(gdf)\n", - "print(offset)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Now let's dive into how to replicate the data\n", - "We will use a model that doubles the data at each pass. That is a lot faster \n", - "than adding one copy at a time. \n", - "The number of disjoint versions of the data will be a power of 2.\n", - "Although the power of 2 replication results in faster data set growth and Graph building, the simple order one replication is shown here for illustration purposes.\n", - "\n", - "\n", - "![Data Duplicated](../../notebooks/img/graph_after_replication.png)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# This function creates additional version of the data \n", - "\n", - "def make_data(base_df, N):\n", - " id = find_number_of_nodes(base_df)\n", - " _d = base_df\n", - "\n", - " for x in range(N):\n", - " tmp = _d.copy()\n", - " tmp['src'] += id\n", - " tmp['dst'] += id\n", - " _d = cudf.concat([_d,tmp])\n", - " id = id * 2\n", - " return _d" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%timeit\n", - "_ = make_data(gdf, 3)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "gdf2 = make_data(gdf, 3)\n", - "print()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# simple print to show tha there is not a lot more data\n", - "# print # of Edges and # of Nodes\n", - "print(f\"Old {len(gdf)} {find_number_of_nodes(gdf)}\")\n", - "print(f\"New {len(gdf2)} {find_number_of_nodes(gdf2)}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Build the ghost node connection set\n", - "A ghost node is an artificially added node to parallelize/simulate the all-points shortest path algorithm which is not yet supported.\n", - "After the ghost node is added, the 2nd hop is actually the all points shortest path.\n", - "The Ghost node is later removed after the Shortest path algorithms are run.\n", - "\n", - "![Ghost Node](../../notebooks/img/graph_after_ghost.png)\n", - "\n", - "The Ghost Node is connected to a different corresponding node in each replication so all sources are covered.\n", - "\n", - "In this simple example of a four-node 'square' graph after complete replication and adding the ghost node, the graph looks like this:\n", - "\n", - "![Ghost Node](../../notebooks/img/Full-four_node_replication.png)\n", - "\n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def add_ghost_node(_df, N):\n", - " # get the size of the graph. That number will be the ghost node ID\n", - " ghost_node_id = find_number_of_nodes(_df)\n", - " \n", - " num_copies = math.floor(math.pow(2, N))\n", - "\n", - " seeds = cudf.DataFrame()\n", - " seeds['dst'] = [((offset * x) + x) for x in range(num_copies)]\n", - " seeds['src'] = ghost_node_id\n", - " \n", - " _d = cudf.concat([_df, seeds])\n", - " \n", - " return _d, ghost_node_id" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%timeit\n", - "_, _ = add_ghost_node(gdf2, 10)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "gdf_with_ghost, ghost_id = add_ghost_node(gdf2, 10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create an Empty directed Graph" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "G = cugraph.Graph(directed=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Populate the new graph with an edgelist containing\n", - "* The original Data\n", - "* The replicated data copies\n", - "* Each replication connected to the Ghost Node by a single edge from a different node\n", - "in each copy of the graph." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%time\n", - "G.from_cudf_edgelist(gdf_with_ghost, source='src', destination='dst', renumber=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%time\n", - "G.number_of_edges()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Run Single Source Shortest Path (SSSP) from the ghost node\n", - "The single Ghost node source becomes a all-source shortest path after one hop since all the\n", - "replicated data is connected through that node. This will include extraneous ghost node related data which will be removed in later steps." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%timeit\n", - "X = cugraph.sssp(G, ghost_id)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X = cugraph.sssp(G, ghost_id)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This result will contain a ghost node like the simple example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X.head(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Now reset vertex IDs and convert to a cost matrix\n", - "All edges with the ghost node as a source are removed here." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# drop the ghost node which doesnt exist so remove from matrix.\n", - "X = X[X['predecessor'] != ghost_id]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Apply the CuGraph filter which removes all nodes not encountered during the graph traversal. In this case the SSSP." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# drop unreachable\n", - "X = cugraph.filter_unreachable(X)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Remove the path cost that was incurred by going to the single seed in each copy from the ghost node." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# adjust distances so that they don't go to the ghost node\n", - "X['distance'] -= 1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Now the Ghost node and tangential edges are removed." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X.head(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Calculate the seed for each copy. This is where it is critical that the original graph node numbering is zero based." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# add a new column for the seed\n", - "# since each seed was a different component with a different offset amount, exploit that to determine the seed number\n", - "X['seed'] = (X['vertex'] / offset).astype(int)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X.head(5)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Now adjust all vertices to be in the correct range\n", - "# resets the seed number to the\n", - "X['v2'] = X['vertex'] - (X['seed'] * offset)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Finally just pull out the cost matrix\n", - "cost = X.drop(columns=['vertex', 'predecessor'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cost.head(10)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# cleanup \n", - "del G\n", - "del X\n", - "del gdf_with_ghost\n", - "del gdf2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "----\n", - "# Section 2: Do it all in a single function" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Set the number of replications - 10 will produce 1,024 graphs\n", - "N = 10" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def build_cost_matrix(_gdf):\n", - " data = make_data(_gdf, N)\n", - " gdf_with_ghost, ghost_id = add_ghost_node(data, N)\n", - " \n", - " G = cugraph.Graph(directed=True)\n", - " G.from_cudf_edgelist(gdf_with_ghost, source='src', destination='dst', renumber=False)\n", - " \n", - " X = cugraph.sssp(G, ghost_id)\n", - " \n", - " X = X[X['predecessor'] != ghost_id]\n", - " X = cugraph.filter_unreachable(X)\n", - " X['distance'] -= 1\n", - " X['seed'] = (X['vertex'] / offset).astype(int)\n", - " X['v2'] = X['vertex'] - (X['seed'] * offset)\n", - " cost = X.drop(columns=['vertex', 'predecessor'])\n", - " \n", - " return cost" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%timeit\n", - "CM = build_cost_matrix(gdf)\n", - "CM" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CM = build_cost_matrix(gdf)\n", - "CM.head(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "___\n", - "Copyright (c) 2022, NVIDIA CORPORATION.\n", - "\n", - "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n", - "\n", - "Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n", - "___" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "cugraph_dev", - "language": "python", - "name": "cugraph_dev" - }, - "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.8.13" - }, - "vscode": { - "interpreter": { - "hash": "cee8a395f2f0c5a5bcf513ae8b620111f4346eff6dc64e1ea99c951b2ec68604" - } - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/notebooks/centrality/Centrality.ipynb b/notebooks/centrality/Centrality.ipynb new file mode 100644 index 00000000000..dbc2a0e18a8 --- /dev/null +++ b/notebooks/centrality/Centrality.ipynb @@ -0,0 +1,386 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Centrality" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this notebook, we will compute vertex centrality scores using the various cuGraph algorithms. We will then compare the similarities and differences.\n", + "\n", + "| Author Credit | Date | Update | cuGraph Version | Test Hardware |\n", + "| --------------|------------|------------------|-----------------|----------------|\n", + "| Brad Rees | 04/16/2021 | created | 0.19 | GV100, CUDA 11.0\n", + "| | 08/05/2021 | tested / updated | 21.10 nightly | RTX 3090 CUDA 11.4\n", + "| Ralph Liu | 06/22/2022 | test/update | 22.08 | T100, Cuda 11.5\n", + "\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Centrality is measure of how important, or central, a node or edge is within a graph. It is useful for identifying influencer in social networks, key routing nodes in communication/computer network infrastructures, \n", + "\n", + "The seminal paper on centrality is: Freeman, L. C. (1978). Centrality in social networks conceptual clarification. Social networks, 1(3), 215-239.\n", + "\n", + "\n", + "__Degree centrality__ – _done but needs an API_
\n", + "Degree centrality is based on the notion that whoever has the most connections must be important. \n", + "\n", + "
\n", + " Cd(v) = degree(v)\n", + "
\n", + "\n", + "cuGraph currently does not have a Degree Centrality function call. However, since Degree Centrality is just the degree of a node, we can use _G.degree()_ function.\n", + "Degree Centrality for a Directed graph can be further divided in _indegree centrality_ and _outdegree centrality_ and can be obtained using _G.degrees()_\n", + "\n", + "\n", + "___Closeness centrality – coming soon___
\n", + "Closeness is a measure of the shortest path to every other node in the graph. A node that is close to every other node, can reach over other node in the fewest number of hops, means that it has greater influence on the network versus a node that is not close.\n", + "\n", + "__Betweenness Centrality__
\n", + "Betweenness is a measure of the number of shortest paths that cross through a node, or over an edge. A node with high betweenness means that it had a greater influence on the flow of information. \n", + "\n", + "Betweenness centrality of a node 𝑣 is the sum of the fraction of all-pairs shortest paths that pass through 𝑣\n", + "\n", + "
\n", + " \n", + "
\n", + "\n", + "To speedup runtime of betweenness centrailty, the metric can be computed on a limited number of nodes (randomly selected) and then used to estimate the other scores. For this example, the graphs are relatively small (under 5,000 nodes) so betweenness on every node will be computed.\n", + "\n", + "___Eigenvector Centrality - coming soon___
\n", + "Eigenvectors can be thought of as the balancing points of a graph, or center of gravity of a 3D object. High centrality means that more of the graph is balanced around that node.\n", + "\n", + "__Katz Centrality__
\n", + "Katz is a variant of degree centrality and of eigenvector centrality. \n", + "Katz centrality is a measure of the relative importance of a node within the graph based on measuring the influence across the total number of walks between vertex pairs. \n", + "\n", + "
\n", + " \n", + "
\n", + "\n", + "See:\n", + "* [Katz on Wikipedia](https://en.wikipedia.org/wiki/Katz_centrality) for more details on the algorithm.\n", + "* https://www.sci.unich.it/~francesc/teaching/network/katz.html\n", + "\n", + "__PageRank__
\n", + "PageRank is classified as both a Link Analysis tool and a centrality measure. PageRank is based on the assumption that important nodes point (directed edge) to other important nodes. From a social network perspective, the question is who do you seek for an answer and then who does that person seek. PageRank is good when there is implied importance in the data, for example a citation network, web page linkages, or trust networks. \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test Data\n", + "We will be using the Zachary Karate club dataset \n", + "*W. W. Zachary, An information flow model for conflict and fission in small groups, Journal of\n", + "Anthropological Research 33, 452-473 (1977).*\n", + "\n", + "\n", + "![Karate Club](../img/zachary_black_lines.png)\n", + "\n", + "\n", + "Because the test data has vertex IDs starting at 1, the auto-renumber feature of cuGraph (mentioned above) will be used so the starting vertex ID is zero for maximum efficiency. The resulting data will then be auto-unrenumbered, making the entire renumbering process transparent to users." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import the modules\n", + "import cugraph\n", + "import cudf\n", + "\n", + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd \n", + "from IPython.display import display_html " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Functions\n", + "using underscore variable names to avoid collisions. \n", + "non-underscore names are expected to be global names" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Compute Centrality\n", + "# the centrality calls are very straightforward with the graph being the primary argument\n", + "# we are using the default argument values for all centrality functions\n", + "\n", + "def compute_centrality(_graph) :\n", + " # Compute Degree Centrality\n", + " _d = _graph.degree()\n", + " \n", + " # Compute the Betweenness Centrality\n", + " _b = cugraph.betweenness_centrality(_graph)\n", + "\n", + " # Compute Katz Centrality\n", + " _k = cugraph.katz_centrality(_graph)\n", + " \n", + " # Compute PageRank Centrality\n", + " _p = cugraph.pagerank(_graph)\n", + " \n", + " return _d, _b, _k, _p" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Print function\n", + "# being lazy and requiring that the dataframe names are not changed versus passing them in\n", + "def print_centrality(_n):\n", + " dc_top = dc.sort_values(by='degree', ascending=False).head(_n).to_pandas()\n", + " bc_top = bc.sort_values(by='betweenness_centrality', ascending=False).head(_n).to_pandas()\n", + " katz_top = katz.sort_values(by='katz_centrality', ascending=False).head(_n).to_pandas()\n", + " pr_top = pr.sort_values(by='pagerank', ascending=False).head(_n).to_pandas()\n", + " \n", + " df1_styler = dc_top.style.set_table_attributes(\"style='display:inline'\").set_caption('Degree').hide_index()\n", + " df2_styler = bc_top.style.set_table_attributes(\"style='display:inline'\").set_caption('Betweenness').hide_index()\n", + " df3_styler = katz_top.style.set_table_attributes(\"style='display:inline'\").set_caption('Katz').hide_index()\n", + " df4_styler = pr_top.style.set_table_attributes(\"style='display:inline'\").set_caption('PageRank').hide_index()\n", + "\n", + " display_html(df1_styler._repr_html_()+df2_styler._repr_html_()+df3_styler._repr_html_()+df4_styler._repr_html_(), raw=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a graph using the imported Dataset object\n", + "G = karate.get_graph(fetch=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compute Centrality" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dc, bc, katz, pr = compute_centrality(G)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Results\n", + "Typically, analysts just look at the top 10% of results. Basically just those vertices that are the most central or important. \n", + "The karate data has 32 vertices, so let's round a little and look at the top 5 vertices" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print_centrality(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### A Different Dataset\n", + "The Karate dataset is not that large or complex, which makes it a perfect test dataset since it is easy to visually verify results. Let's look at a larger dataset with a lot more edges" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import a different dataset object\n", + "from cugraph.experimental.datasets import netscience" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "G = netscience.get_graph(fetch=True)\n", + "(G.number_of_nodes(), G.number_of_edges())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dc, bc, katz, pr = compute_centrality(G)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print_centrality(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now see a larger discrepancy between the centrality scores and which nodes rank highest.\n", + "Which centrality measure to use is left to the analyst to decide and does require insight into the difference algorithms and graph structure." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### And One More Dataset\n", + "Let's look at a Cyber dataset. The vertex ID are IP addresses" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import a different dataset object\n", + "from cugraph.experimental.datasets import cyber" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get the edgelist\n", + "gdf = cyber.get_edgelist(fetch=True)\n", + "\n", + "# Create a Graph\n", + "G = cugraph.Graph()\n", + "G.from_cudf_edgelist(gdf, source='src', destination='dst')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(G.number_of_nodes(), G.number_of_edges())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dc, bc, katz, pr = compute_centrality(G)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print_centrality(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are differences in how each centrality measure ranks the nodes. In some cases, every algorithm returns similar results, and in others, the results are different. Understanding how the centrality measure is computed and what edge represent is key to selecting the right centrality metric." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "----\n", + "Copyright (c) 2019-2021, NVIDIA CORPORATION.\n", + "\n", + "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n", + "\n", + "Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.6.9 64-bit", + "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.6.9" + }, + "vscode": { + "interpreter": { + "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/img/Full-four_node_replication.png b/notebooks/img/Full-four_node_replication.png deleted file mode 100644 index 8cbc3cd1dcaeb7ed4cb08613e353ef3baab61d13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17291 zcmb8XbzD?m`0on>f{c_(cSwgIjetX{v~&%n0z<=)5;An7pmZx8(m8}Q2n>yMGjt8o za5vxYFV4C5+J1B>~_e1 zhk+r>@LCS6^z#$u}%_X0`r*s$>)Fu$ZYuRLk-A$n)IQBt^9T4=L% z#iR;bfQcN>r`-1S^`&g!Ji-PO-}A?7V8D}kNem{(2IJu|q2LU7m|$#UVr)i+&`c1V z0UMhH1||;t!~o~TgA)_WA#s38@c*w$xQV}h$#^XVkz4K0BivqjZ~m!dL!2!~{QWGQ zB=A1mbzm7YfuQ|1?A~d{(9BOWSqVXbS4Fv#F*y5-@I4oX>{)#?VkZS`#7>72lFzt7 zYwLyufA;ZgN(B>qPg)M?^Dv?wU!1R_oj?wmwnU_RGS4Ej%H>Q*0=ot3Tl7gBg_AR( z27$(hy~LjDe)*#P0sBzeQ9Q`qo`#wJCgjkYSDY(klm5voA`R0v3r+q94dQ+vkgkbB zNeDE*W%@qh#>KWPmm0Nb^iX@(Br?o#SFjq_b+lcpe3b}8ljfywV1eE6@XQ0*dLvzg zN`9r+fO@kRih0+4{ z%e<~XK`i4-zLn8~=9vqyuNxbsk;^Bz1uMuOT4l1r*ICy0$M+L0X!-u2lSua!kz#*- zxf-!yf+-@za@_5&DNt+7bej}cWeG(a!^6jpX(z&{wPa*}crL-K7>lp3T!sCQ^OtqW zz#>7L=NeMc%x2;tnjg{I%s{7qQ9#!2iKz(9{Qmx#xdIiJ`WjYXdT(w)bpE1|=Msxb z%(jrUr5e?g*6x=nTbQQ1U$=xUNB#=olG35hM|AkAn_;jwwL^nXmRv7S7-Lm?2$Rc( zS{Y8am-=w|EY%h&Axih2Ki668cduxRZ(8cJG+AuPI+mbJwZ3_oGWWjkbDs3boY+ta z%H_|5o7ec_Pu<1&@ERcYEb8}<3c8r?r%rzoW;Xiq!j3&A(}hl(;l#$j>DA)`p3j4w zs5J>QPBp+BHUuf%Gxx^2{rTp6!vh79NrKIh;2(zPSE17!D?BK#^M;6IH``1VnW?8BjL^aXWGyyGuAnM z>a@g4MxXY-Njkn|zhu5=$2@T@ci!s3i%@v@S(QjrUY?w?G=`TIN<}LbV;`wW%^>Jy z99vraZ=1hky$Z3YHX>XReE4hZ0;5!7LFCh|HorVowKrSpR&*28Q*>fG8&v1Z?Gf`5 zDqX_@EYL!8@N=sY*PL*Ye*VyQ=RKsh7nV=$7K9K-9jEw=0W|r?vkfE;Hey zX{Wdrg}m@aGnY(x$2WZE&ACtOc_<_7N>vk&FD-803Hor43q|doo`orEN!dvyb921b zZiYiU;b+>Q9j$J#xB_MwE@BU}3Q*_&ON)3}=GOaTjHD99S6S|1mW==|*VN?X_nrHv z2ep$;!@rLoRz;%YI$&sVoaYFc?;;~m8J z8(o+e9JBdWY4RpEkYF=P?OgBRBSv#pmvKh%s}}Z*Yh8%1F9yyZIqk!Ss`^OaS2-v& zQ3<`!IQ39&04ZNC>`cS;0JmsI3(nAyTyQSIz7c@7^dXcfEqmF%2!apZVnqk6MP|ro zuKU^su}S!o!fgI5FBU%%X)X@Fs<71{URLb8Jl;IG`m*r7-RI-Y=Geqrr=EqKYwyO6 zRo0(y!e0ozC%v(JKXlWLzcAn(W~yHPL63*`z)Mx@86Z)zzVXc1M#u=0>mkmCqT4KQ zc}3o-r%%}~BBZ>Fo4?Qzo$x=%Z^;vi|KNcfJK<$bP~@hZoJ<2 zHdCr1%_vHWT0WWuLK^3|?;4AQOI07-Bls;Pzw*I8OQi3ok9|w8;pW_M2x_`kZRg9E z$Hqttfw=>HY}24{ZvhHd=4kd@;_cU z*~Akr)@AF={Q1TV60K6FHELbz>={FwnjM%Il3x^tc#70z2Y|%uaz~Ak z1J?)mKL$_sILTqcFncnHwzvMfDVUi*Ln|6q$i4~RV=;pIUcV64SrOw?E6VS>tQPf& zMQxrC-Ae_nWgDj|l*NQ^zL-@&*anu@708sNM-Z6NguskgQ|5VTN2~pv($rb|bVie= zdv`Te6icKN%=#=jrO6T}vJ%GUH~BRuvSzSVk!c-GP0Ac~FGqE~t?4~Mdq^+K=MTP0 z?f28_6RaGGWr|q=v3$Xxim=b!z86clbOpv?8fZ~;8OX6_TSj{u(etK^J`=5li8B@SC%*Rw|UK^cV z4gWf?e{t36!~%9NETW6P)z-jL^`yir=B>-mOIGBQm7XXAS%}xp3)8M@Y-6bhdr3P* z!Z-4EZjg71uiph^Ypl$+k*E2&sZdmgO>=Z_ z_;K7G^D@W!?{UKzj`w4X)5qtu5*$Kv2t}5(PX4p3PR45)J6=CV&+vntTh(8zL107InHfLrOTBPxo~P)9Jdk+x-9k? z%$XzddG|N3f=Z?R6i!GIl1QEw-h!lSJdhNksEScF_B9Qui-^-p5-*8;P28c-#!u;liKYJd zgT1bfOv!G`ewt#cOE1g#Zep|&!n4a10@$*38&{D(vz|xNMFbP5)Gjam!RvumeK|8g zJ_b7A?@)8jx0PsQltg)29v_hx+Xqo2(l?>e)B3}npSzxP$rOT$con$c71k}zK8x1G z!R&_mW^UlBZfN%}3qq-LNW>qrV|ki+%_!fCaLJz>7}Lqj!@3X}1JiYE=jO)brWp~M z$bPEHw{|Q}hVOBIKIcHF@bbyY1++?T z*%0h5`#Wj8(Luc|@-Z6C=B|jd)De!OCPYd`Et$rNL6Zk6;=ITG!M5JU5mlT^*O#YM~ph(JXnaTo&Q0 zlD37vBJM;{Ti(Pn3b=iA^IDBT4@=5#&I~G4_X%1vR4w(@#UVol&vvm0!yA9L%Cu^W&Th)KIdYF;k1{q5dF{4d1{ka?awEWb+ZH$3-c==REgBN&WriK9kQQ7+En;0S$QPX zp6igt;0y0vq(baTFQ8K&pCN=%JYLUBScPbH|6I&iAyc#vHv8&V4~qyOfVxh3S$8rl z5=}yf{l!VhfXV5agHp)G#!4<-)`yjBB=BF!R=SpNJVC?(F>~Crrb@4lF04wDi^6SD z9q2-=T7@c#e|M)QfjMNW1+>DQGrqXq+0jOJuiECgU-Er-`m-IyR7ySa1D)ouc8t$l zDWZ7|3E+6Y)Blfz+k)*|u00NX$N0I$)6?#*stU4P*Ym_pOwi2@W zQvWd^_=ZxmlYL$_82!~i<*ytyHsgF<`KLCeqTxqjHh^q6y|*Rh<;l_JdxU0_z!~nA zXH)8Ik0m;v%hed7fr@78bF@Kzx;CW2QA=@`KbFmULayM8Y(L*Q{t3y@NHGi2D#GG} z^!%8R1r`4YzW5>|(vj+95EU%WdwxJ%O;ZsIt;(?GgCxG@xzG2krcJGm33V`dERogh*B3BS*zF$n<}9kVt?!)|m^O4SkrxeEJv!$nvNxwQ%t z6=Zyie9_ydR3AEBsIP*#;PdVy_qS4O#@NSfBSdxxxKPFlyg9ycB7dH;rq4gtRS#(9 zc@riWXhi#UBam zD(R!OkilLbQ{k9^B+D#E*G*6GS%QIb<;W$0E;a%@=}jf>Q+?=fD&euv*%y**BPrI^kv%2dCHohX{b z<*C?~x1iIGpKl|Sq2dkD0^BTac=Q#Y5cx(8+`Vn0%1^mB(@Y~=jC$mj|LN#s!x^$# zGlf^*ZKyA^;us~5&aFQ}Cb1=Fx5zLwL^{ho!AO-N!#;sECOEH0ntjfsdP-JAq|dW2 zj3$#BMY~^Sb5?rfwKfocyQC<3W6NZdFG0_;J4wL~Q#={LKT`6HK8O5gym&|y9vxS=qBuVPc=qFp^I}!h zL`?EllqKlYF+Z4IyD`QPn)d|Tf!}&wbt~4O$-_Sn%s9mg?!I?Fa2r1Vp~svmqjs)u z`~Kq%yByt1H@}IEXLY|AK-X_cVbvMS)WR3f=qGpQK6kC!mn)%#OYy|NZ6W3?!C}-U zIp)a+U(c_b#};g)a%nq88rJwiUpt4?I@mmVM43E#E3hF~yx z#Fl%1h0-JJeSeYq(Ta(0sE-YqMg0*fz~HiX^FXXqYF$KJ<axf_kRO#%zvJeGrc^bp6NTNt?Ihpbp@7n!3C&ats`jx*wS`sr>|H*_&;JlYvnA?_`(y2+<+}(; zP6%~HNMTjlBd5#RNkAvhycT4!Af;_|%$TR~jjf@(nc#g*+=R2n`YmB6bNZu%3EHh=^?6#LCrJ?72tCGbYKjcyP~f{Z|_fjoByPO4SiX+&RA=6nm9F zbvwiO_6_awr&;@*-(iN36$XGroAT!n8EY6=(a#c(Cw()&tq>a08DY2Cn0^K66ueC2 zFUihn;WB>&<12P<<0K`sjBM3rSWe`d)aW$UsgK91vkXY7A;U`0h(dbtIQlAEFJm^+pV* zVtf@PEoo|FmT{#i!j59}eqP&biEch`I2y2!kRzVOxe;D+uUuPPo8S~IUWKb^oKXao zX^hk7>FVY?hhG1|JZC4Wia@?=hp!vMLUd*XZ}D_B7HIXVc%Tq{J?Ggy`Hp5oT~95I-3_(iln< zj>Y{+U_X8LRkpi3JQn{3l;5I_y<88Zrd^f)`jK486YJ)9etr2s{8Ogvltq{>U1VGW zoqAu7EPCn2;m#W_*eGgU{vI1D(b?0k$G5>%#7)}PbbgkLsOtxHOun&IRi*icEAW_2 zYU+Tgs8-Hz`DVpGgwf&Wo8S5Iy z;Gl^HEuZUxN&3hIKQ=0%TW%HJ*lQDPKjPJKD+mLy!9QSr=o^VCL1>(9K)dl4K^LTj z=>AYR>drP!c~Edd-d){5rg!KO_i(YTRf)vcgLp|Wa7}? zo7@Y?5rht67LJ*_y{eP`?u8XjLvZM4FO}k~Lx-LV;Qu)Hlq%Ne%bv9JX~_$4XfQ*W zf=k>>ka%KqUFa6f@J;pQ&a?13`&XxLF6HBsjaM6SEtX80v-d`0Mh7I+c7YCN1)D^z zllhaub}X(!;v@4rfgsCm;+M@LpV%br#!^E*rw*Iwx5Vc1NT5;ROdN#lw0PmPXSGHZ zGv!e=efb1$uZgXgV?=WF3pqAk)(s+AxF6n~q<^dj4k2o#qCJW-M}U(W8+|Xs_z8-k zGoHrPQ`hQZbAOcXveJZ3TcKcbK&y8Q(&T z11|h;D%Ge~VsER{BZ>um*LKE3Ch)AT=c(`J5eO1B;c`S+ILjFW{d>_fMgNC523C+X z^TjtQ8;EgSf32z4%GmE3u-Fnke{OKoJ9%d$!c0FyeHWSE z1i3|YHS6`sY!XflsSJ*nH)`LM8tdsVjzusT z>vkl3$ElnqcW(>IiNq;KZtLYsznZYKE;6HyyKuomp<=mDm7OTEbgk1Y5>S5WS7%%V zX)ehEI;2Co_;7NM>Ta3Bd7sfr-w?#Dzc^{Qe*%6tBmEJm6I%z#e`=R3+Z~XJFY3$C ziS<3#4D^epA?nF=sO1kMJl4ZYWlCKfEne?M(l}e%X!V?~lQmt`i>KWwn?|Cg5_|+3Wp$vJ~%da>5g0rDU z8LaaEM%T*8L4V)9&P5pYVa-?Bi0$E}U2bIo-y&gck6`Uv-yGqkeD8?CeyYHG_}i#C z1`0Evsm^CMjv9<8@$T*D4r%!z@|FlW%-38ii_Lg5whiY<81Z>Nf@f?DjpQI#p2K_1 zfWo*;vB)^KI+Y#LYgiaeGJ2BMBzqz|X*Lu#15ARB1WZj4Lusfpy%sq)Mq~%$4(El; z{j21i0wZqrN1qknb6OwD5)8De)Jw2JSz3G1E!VEWc#&q*=i=BfGaRsLU^OVkHK- zHCJ&PLF`BMSI^dH^HJ1NXLf@z`P9<~ub-l1(6e>d@eL{xsu5piu_B43f8v6KC!oSx z7~`nsa-*`FmN9{Z8hYhDhjC6IXT^M=7JlBpb5fC#Ml7M%vt)+nw*(Tj`^%m#I6wY7jyGGZd2-wV15x4rY6yh;7XN z7|mxg$K%1?Z zKtIJOQ<04?=ItFG+Zck^plPC1OI!{BC{QmHs?h z^JaaqVONP&uICzXL#)YgFTh!K_;+=M*%uQM##6q#JKO$ZIVT98I^`(K* z+16ml)D(!?`@y_?;0sZ}jT4UHzBveD+sWktA~{-Qg!6XLiuR&}cN5nHWgY2(B4886 z5-8N@pl83kIIrr6i&x9JO6VLdls{dx18_I!5~*N+P6erR^lW`w2jo*4*F=t!Tcc7((W~7iyRlQm zB@`|Q6<2p9qNIr7`vws1!5sMuj?G7Yi!)X#7PaRm_nJd~oB6?{+t1(*$~w<+blX#z@{5csCi8 zWhe;os7H!q!fqMewom83uUim~c4zqBDC1GyU@Q_5O06HuSe*#C@(Z^CDT|`)qJ*qT zOvlT^1TZC3%D&P-xbIVJ5%ZDX&BConp*ZLsGteVX$OK)nf8XoMjY#3NeaT}n^VwmO z{&p<>rd#@oPvWl_6k?2Yh4H)HTPx3WN#Y*#qEvlfxTJdWcH8&xg4ssyMrUj95wr7X z^^-uyyU86S-}73}6>~DipBMhsq(Ij1Hs-7KY1qsJe}D$h2tTm~O_5n-+}Tz>oUl&$ z#)L;!tv}yxWV?r9;P(7h1-@y!Qj26Dy0WDD#*fd%r6!Z%p!9^n^mZ zAAd!IXITsUj^qQ{f=wAt7)zduFB(xTUa?LhHc7kKJ$SJ@nsGh#r^J$;E6SG^?R~a@ z_GiRXy)zonsUl?DltTh*)ub;x$*3EN=DEL?e|QlRMzs+bms#nteXrkDJ8Gz(+1V#L zRoWZvwmqp7Lmv1FsNB4oVB(iobyX=?eE_F@n;bE@r@ohMJXShs1f3EHxIqdAP}K}p zM7!wH{9In~7q4d8?g^3uUe>eZ*Zo7ID4Y#c?SgNuY-n@+b!@Pjo(jQylB%W&RJG`) zfOGC@rX|PZW~;2YMKt}dgG11A-ta+~jm)OS%7zy3HUd$_w$tB6AH(jirDli3_~q{6 zHh)ie`vJKljffKa7AkycLao2~xvVMm?5G*9e(`~fS~bI2#yP$551kdlw_oF7;%5uD zKjIxjo_{YJfrX{LJBBz4XiaEZ!Fe zEWV4{GHR88wW_5EcEHcqM_UyXM==zFL%ijJOsnw82a>(;MThWc<1E&)7_O(twQ|<7 z=!ak3$9d~1NSxdF0@KaG2O=d6>qy1hgS@xPMs%`bJQ9OdeWZ-!R|+T&GVELDgH<8u z49u3pEH{ce;xT$tQ=HIE(J%mlxTvhFi3-siY^VfW?c+(d!52+FtUKh{`h+dxti5NB z%f!NKrn9GB9~s@S1y&+&Mbrbp;7HA+k@&?apA`8!x+IGZ0vK5F1-1#n=)<)vBk%!r z7MmXNmC}l^K5SR|dh%`d4m&Cl`rx^ENLEu*oJ=g& zH}`2o)tLzTK8n2$rscr9Y4iPs)0XiP>32nRTOP<@NT!O^#@v8aevkw`29sZfUk8HV z($=%~WX62`@gVh%8G*?v`XvP~yqFgwWH zg@&M3DZ^P5@-a8wO{YPPmDk$BYosUXLwbuU!q|C2SQ6(6pXzLCF6o3uNyhS6H9o@*)L51fuGA${kSCb$1GhB(%<2ibj*@-tZx^_K8t~yx(~r@Nn*c7>i6? zZ|4_K3oeL1I=Cj19zF21EVjd(T-*o3fGr%A?~1gs|TPU;1J>g5)rYm!%_^Ls7`qkm{7 z8>RsL9-*4=)z>bb8~EFe^xQ_Gcfh}HLNjrRN4-V=s(wCqbZ_;s61u^oZ9G8SiFh!! zBtwsPUF{#2WkN){*Qbeozyl2l_>}(zJ>lWEbH{y`lay{WWPKyPemxtgQ7(@?XKC6| zG8EFdi7mrU4UEo2ExT>!N7hVIYj2c{Ly8?Mk~H}$)+aJ&XwGm-?L)aBr} zF(BxPN&5gNk;LFrJk{eRrscNv2?V|D=>>}l#Mj^VYl%k?KEQo3ZF!5z8LhX+CFtAm zX#gP=)QHMC26gc>2 z`|wM{;A~i1g2nTEZ;zB)yVT7iMezw-_i{3?>x6%zAr}UOyz1Pe$Qz|&fS<&$p1coFQ zPfF^DM~I>>MoQeHpYVY&{i^+sswI7x!yI-_=yov7y8VORc_7PUw2vYmvdGw(!?lC^ zqTkj#D#gkVxM=foKj*V zrgy>rrL7V+G-Lvp@!}MQhr*juA%i?TlI}O#A{#s!8y3ck+Oq$_7y(hQi}|5aq=e)f zzo?Zm```Q0k{n-ychmNgbj09`^0_5v6&_skGdJ_$wQu^9&~sGBFx?XoljYPi_GEk0tDrUB2Z z6-RRI`W)uryIFr+fhT?>@=pOjfvOPog$>s{E)MS=mi$90La&K*pWwEM%Z$g6CiVSJ zz%bQOe?=!oz~_@muAIaK&~d+*0;+muphD>hw%1-_rf|X%O!4u7BaA zw?~hxz}>U8)`Z3)i)h)1-&r8vwc`k){N0Y6s#Z3`L$a2O4DC|g+s|dF9-PMSt51<| z`x0kL=^tZQnS=e9dBbAdAFtl&uQ8o{yOh3Cwm!GCM*nh2_Zy`=*%mir`$5Pxvu;Ft zzbun+vqFp>up&Ff(Hd0z6WSdah9-MGN=IurwXTr;Xkk#wW zj|G}76}vTOgP+C)efJ=ZPr!JD(l=MsDtbpa1f4ofM6+|@ldpgG_s7FQfILs zJir^C^{KynCZslo_d=uuE;W-99^V@ni7C?nHqe{-N`{9rzsn_sAfe$;Wj9i@&JDke z{*lkbG0sT%U*jK5sl>425)VxxWP=)j-GpJ@;4rKoIzBl*I0Ue;X?}wik#f7EdjQ6k zSLXar+$b%WH}`@zVqt%Cl1GqeyTe}s-!eds_{o)fdi{$qhQDIkTwJuLF zM$e?`y+aI($_Dp}QP0L|y)fXV)*%<~i*bLtHV$zO{N$|`eszFX$>yr6Rtr%Mu$%MM&0`-kDPHk&ZkqGh1v{#f-&w&}$zQkP{KT3@R5gD0$QvvD086c& zikE^e{u_cwenb=!$7mcNl~QmjxQ}Pv&kvAB8*%`xqf>y}_U@H#Wj}jzwRem$oWXN2V^(`AUToOBtJvx#acoJ zO{BFs35q`?w56ZwZxv=**i1|3zC-?G|IZ9fSczT4%rj<|k?JTS|2sOO_iqz?a#O+@ z%T?9TAe(3lPKc?ixF#BWcI=e!xj%nlRmCkVEbp{~bS%>WWMSfcw(9!;9I|O`N^nsP z#Na2Uo_amj#X-!VgvLIgvMogr+`-<)iX}Bl&N0!<!N>AwoBmKvKc7tB0OnWxa`o+2W*O|#Eq>K+z*cy8mK zY@LT@1(Ni$4MHgKjXwms1Vr#~4uWFHhLP@E`_gQ+xC;NgcTI|WnZC7((@*FwTWEwt z+o+a3e}uvi=edQIKENw*RXeIZq36_TK4-`pPTEH2}M6y4++p_R~RRWguj!t(8scS!es!-@R8B z&b4~h*liInktgFk@6((Q;h8BqwWftQ=7Zg+%B^xJ6UxkE1ra1@dejG)C6F#{MLWhZ z-fknvrvYY%6|tcnmsLjPAQegfFtfXy(QgIfsY3-mU?ON-8~uqs(BwVKh+*6zhD8UM z_2FoW*lV=d5F)G-otHMrq`KH?7YfG(Z4;f_#-bALK&o6J9vi!`6vB4dEO?o{7nSg; zqL}^Rn;C8|8!dvB0bM^MOs^NzKynFu!1*t2B9}3mCyN==p^bK!I@72=#Xg;{qY<^s zImyW_AUdkG>Z-CO`A_8J=x!9|Gifq3-)5Q-!@Mt!%Wt$*tDXK(7dy}1+prg|d0RkT z<`;zp^?pFD`Da`hPln~E*8Z*(2movh{*x@3oyE-&Tupc%L1_9;a^#uJWS$YsUzdOx z4YixQ#1H&@GLuvIwc+q00zYyyj-DA^Xa_`h9dcUqw8&(e6(na0jK`xH?>22+MPxE`+?6-fPY8WsW*)e=e?6fHa*I^ISy>qkY$y`ybdq%Pid znUQbtvF|>VVCPwoU^d+OeK8#?sr6$wKEHN|_Dgx+;I5C+V;IsxxpV%>u0Y>4Xzu;k z7kmJ0un)X+iGZt|bXR?1WDrh3l+s_#G+7@uH^6--1d} zi*UlyO|4VAys{TRY19_}B{&=zq`lh2E>WJ;$pndtWmN-J(-^uiM=U@z1{WX9rf7C;TZ`yaq>DOy%R1{Y4{#|a);Y9Xvx z&EznKW9QB$;3U>qrKQ?s5A_o=vJntp)5`>2V@{_gcZYK3I1m2!eesVrNSko(%SDCk zh2DBz}>x5=XD`L-G7B75^3tmsFi7{8IPz|{+gY=)= zSkptUaJ=&+M(YJQYe)?;cLd8pH1Wg(CBLy8!>mlPX+1n4s@TTZb9^jE$>x1))r^;$ z6LV8M8}hfx-`)Xe`~``Ep9_~0nPv%nxM>(2^237xvqy6wRP zC64zu;7&7BW5UjqM!B2fsNIR7fQy2|R9!nZ>W%Rskn*H%32tk>I`4F+HIuO{@m@2= zm{QcFw8y#B!HJpXcDIw41lk4j+B-iZ+SeFdXM;cHIO^Gtb>EC4vJ?K!5J>v7gK;5g z2`hv7AiVZ8Egag{yUN^DP)bxhx4|rUJUZCI_=Q#-)^>L166dR&i4b|Uf&TM4)z8>z zGbIz0YZVw-w$%tPglG$|9)K?owOfxhtX8`Az|BcRMY}xBG>SYMs?s!491vADqDop^(<1L0#DRjOCb(=xa;7SX+`o0{7Bbs%WX)3N?{o=q6Ck_C zmZiC!6%pb~3j+&frKdz4ON7PvLfH66X4KPdLPhf~m*aI?2$%M-YE_2X^Ig}SmAfXz z-hN~<*3>lOZml)p2H*exVOXZ5ADCtamAw37my{iSY<&D1NZq24{OL^!E*r=OW{up#EMwhBTYJf=Ws=em`yh`-I%zp?01Sdw^ouf7+1nMQ@;A%vB*+g0kDi zi=sUZ!h@BTB=NzsqlbfT4Yl#>w(!h}Dm7v0S!oQ3h-s zoA2|x^CJvcC*^r6vs*t-(!UkRCUX(~erxe2*W(s{=_>v{wkyPbrPKxOTx44~UJ?sM z&pz@Z4&PWis8xs=(5k-{D4Z3TITFD$7vt-uZGAU;IP5>>2qU}FUWy(Hw#a2Ne*U?aUySILXL0PClM3WiLeVkfS5PYZ7GH>I11|Ih`;ZX!MfI<8{5}G52XWl3 z;Ad4hiKVp9#>Ga?KT#2B@*9u7vMXk%9huM=A1sQ>rUhu->(FYq*S$(-d4d=RO-4h# z68X+WFAD!`Gp}H^0ec*!fmzmLXA)B|d0=?OX(g9dj;wE3bKNcMFtqV@C+_L5A&8Gv zP?6csLc0z9HJ0c8!qK;7ABtM ztJ@8-dL7?3Am&QUMRjT*SMBA%Gfw0Rw#4$HIBJ=2`AnKn44i>9*)5MPbYIX~?y@Z7 z6LY{n=q=0jzawwp#3leFtK@pI$2*SO&xGoUalQ)EKi(4m|T8|B!nFoEHtrN__{M(VDmU#acMw8gSTEbYGc>pxB_6V9aual`9H3T$be# z*j%J_%e9S%c(YBz#jF3IDsyu8MFzB`1u+(-OCbGo5tl-f22kg&sg^*jOXfxEZ0+wX z-BiH8(txGLobM7qZ({|Bx!&ER5e=BcDl+={Cg_sZcl7NNnEawBTwI&8$mk0g*)KQGpYova&_-}|4~9S&gh4!3TUcwGAL zq^6y;-bKlXUbY)|T;_49d#*9oiScQmxg3&t<^BIFwG+4j7y(c3sO1~vt-YU3wGks2 znEjAIEY0M~J?hKxD#jbquHyeTEGV$S^U8J z{Or7hBGnB?1aWTe$`wsn3a{zIWp$!Z$~>V5)P9cTOS9tM1b^y$0N8tD8OEAz9YMFD zMTQ&#tz5!^GqFX7;<@7TGJa%JXU)rgW3no`^B*&7KKCP!3!G%GUU7nP9re)B0lO_SA2}LqZW7)XI{&lR@3mzJGWv?9CdJ_HFK{{ z(#v@K*!kRh4%391O`Tk59_CBa&gn%ItKcK`*@W-;tSgy$#IYu0$CK+e%Q4k=TNt_C zULk9$Wo|DrH{_`T3o^Mi5yV~AY2vxb_Y>ui*nMwNk;xqTvJG{@pGMzZUWa>S+j^G#Ty-dl7&yNFIFM6-Gj}k{ z4Wl}ae;B6sc&na5^ITk&R+F41?qyP_R*VOpmp=d3U0Cz04aWxQ+#__A>X3?PomAoG z zjo_Cn0`?shwdw6?+UtPciFnPe%oHi;MKMy4rXxw-zA+|`)qRDzG!}L;7}2oh`pm2_ zWL`DCC?Uv0hye7%fXoCpzI#z`0GF6v)k@ipKWzwR;wozLeH*v#g^|mNUJC@z1zwhB zZ^YW_#Jz`kGuc00hVXiL=7lC3Vd&5x!=Y$WJ2087Hdgm=EZja@o$ zq{oKe0_oR4jJ5>^^stD+pa2sgaP%Ec{0wbPoG3-gg#Gyc^!FkD&wo^cnE2YS0sfL- VeO+4y_yG-!*Yc`zr81`Q{|oXd=}rIu diff --git a/notebooks/img/graph_after_ghost.png b/notebooks/img/graph_after_ghost.png deleted file mode 100644 index 256ac9b5425ccaba8b3ef42ff40b93fa639e8879..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37330 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 diff --git a/notebooks/link_analysis/HITS.ipynb b/notebooks/link_analysis/HITS.ipynb index b564acb565e..91a78473dae 100755 --- a/notebooks/link_analysis/HITS.ipynb +++ b/notebooks/link_analysis/HITS.ipynb @@ -12,13 +12,13 @@ "Notebook Credits\n", "* Original Authors: Bradley Rees and James Wyles\n", "* Created: 06/09/2020\n", - "* Updated: 08/16/2020\n", + "* Updated: 06/22/2022\n", "\n", "RAPIDS Versions: 0.15 \n", "\n", "Test Hardware\n", "\n", - "* GV100 32G, CUDA 10.0\n", + "* Tesla V100 32G, CUDA 11.5\n", "\n", "\n", "## Introduction\n", @@ -139,8 +139,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Define the path to the test data \n", - "datafile='../data/karate-data.csv'" + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { @@ -158,6 +158,7 @@ "outputs": [], "source": [ "# Read the data, this also created a NetworkX Graph \n", + "datafile = \"../data/karate-data.csv\"\n", "file = open(datafile, 'rb')\n", "Gnx = nx.read_edgelist(file)" ] @@ -197,26 +198,6 @@ "# cuGraph" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Read in the data - GPU\n", - "cuGraph depends on cuDF for data loading and the initial Dataframe creation\n", - "\n", - "The data file contains an edge list, which represents the connection of a vertex to another. The `source` to `destination` pairs is in what is known as Coordinate Format (COO). In this test case, the data is just two columns. However a third, `weight`, column is also possible" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Read the data \n", - "gdf = cudf.read_csv(datafile, names=[\"src\", \"dst\"], delimiter='\\t', dtype=[\"int32\", \"int32\"] )" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -230,9 +211,7 @@ "metadata": {}, "outputs": [], "source": [ - "# create a Graph using the source (src) and destination (dst) vertex pairs from the Dataframe \n", - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(gdf, source='src', destination='dst')" + "G = karate.get_graph(fetch=True)" ] }, { @@ -379,9 +358,9 @@ ], "metadata": { "kernelspec": { - "display_name": "cugraph_dev", + "display_name": "Python 3.9.7 ('base')", "language": "python", - "name": "cugraph_dev" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -393,7 +372,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.9.7" + }, + "vscode": { + "interpreter": { + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" + } } }, "nbformat": 4, diff --git a/notebooks/link_analysis/Pagerank.ipynb b/notebooks/link_analysis/Pagerank.ipynb index 2ee8ca045c3..ebd8d32905b 100755 --- a/notebooks/link_analysis/Pagerank.ipynb +++ b/notebooks/link_analysis/Pagerank.ipynb @@ -13,13 +13,13 @@ "Notebook Credits\n", "* Original Authors: Bradley Rees and James Wyles\n", "* Created: 08/13/2019\n", - "* Updated: 04/06/2022\n", + "* Updated: 06/22/2022\n", "\n", - "RAPIDS Versions: 22.04 \n", + "RAPIDS Versions: 22.08 \n", "\n", "Test Hardware\n", "\n", - "* GV100 32G, CUDA 11.5\n", + "* Tesla V100 32G, CUDA 11.5\n", "\n", "\n", "## Introduction\n", @@ -129,8 +129,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Define the path to the test data \n", - "datafile='../data/karate-data.csv'" + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { @@ -147,7 +147,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Read the data, this also creates a NetworkX Graph \n", + "# Read the data, this also creates a NetworkX Graph\n", + "datafile = \"../data/karate-data.csv\"\n", "file = open(datafile, 'rb')\n", "Gnx = nx.read_edgelist(file)" ] @@ -187,26 +188,6 @@ "# cuGraph" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Read in the data - GPU\n", - "cuGraph graphs can be created from cuDF, dask_cuDF and Pandas dataframes\n", - "\n", - "The data file contains an edge list, which represents the connection of a vertex to another. The `source` to `destination` pairs is in what is known as Coordinate Format (COO). In this test case, the data is just two columns. However a third, `weight`, column is also possible" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Read the data \n", - "gdf = cudf.read_csv(datafile, names=[\"src\", \"dst\"], delimiter='\\t', dtype=[\"int32\", \"int32\"] )" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -220,8 +201,7 @@ "metadata": {}, "outputs": [], "source": [ - "# create a Graph using the source (src) and destination (dst) vertex pairs from the Dataframe \n", - "G = cugraph.from_edgelist(gdf, source='src', destination='dst')" + "G = karate.get_graph(fetch=True)" ] }, { @@ -446,7 +426,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -461,6 +441,11 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.7" + }, + "vscode": { + "interpreter": { + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" + } } }, "nbformat": 4, diff --git a/notebooks/link_prediction/Jaccard-Similarity.ipynb b/notebooks/link_prediction/Jaccard-Similarity.ipynb index e3c6e7fa4cc..1c94ec2a023 100755 --- a/notebooks/link_prediction/Jaccard-Similarity.ipynb +++ b/notebooks/link_prediction/Jaccard-Similarity.ipynb @@ -17,12 +17,12 @@ "\n", " Original Authors: Brad Rees\n", " Created: 10/14/2019\n", - " Last Edit: 08/16/2020\n", + " Last Edit: 06/22/2022\n", "\n", - "RAPIDS Versions: 0.14\n", + "RAPIDS Versions: 22.08\n", "\n", "Test Hardware\n", - "* GV100 32G, CUDA 10.2\n" + "* Tesla V100 32G, CUDA 11.5\n" ] }, { @@ -221,8 +221,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Read the CSV datafile using cuDF\n", - "data file is actually _tab_ separated, so we need to set the delimiter" + "### Create an Edgelist" ] }, { @@ -231,10 +230,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Test file \n", - "datafile='../data/karate-data.csv'\n", - "\n", - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" + "from cugraph.experimental.datasets import karate\n", + "gdf = karate.get_edgelist()" ] }, { @@ -271,8 +268,8 @@ "outputs": [], "source": [ "# create a Graph \n", - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(gdf, source='src', destination='dst')" + "G = karate.get_graph()\n", + "G = G.to_undirected()" ] }, { @@ -472,20 +469,25 @@ "---\n", "### It's that easy with cuGraph\n", "\n", - "Copyright (c) 2019-2020, NVIDIA CORPORATION.\n", + "Copyright (c) 2019-2022, NVIDIA CORPORATION.\n", "\n", "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n", "\n", "Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n", "___" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "cugraph_dev", + "display_name": "Python 3.9.7 ('base')", "language": "python", - "name": "cugraph_dev" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -497,7 +499,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.9.7" + }, + "vscode": { + "interpreter": { + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" + } } }, "nbformat": 4, diff --git a/notebooks/link_prediction/Overlap-Similarity.ipynb b/notebooks/link_prediction/Overlap-Similarity.ipynb index d71078ed061..5b3633f4014 100755 --- a/notebooks/link_prediction/Overlap-Similarity.ipynb +++ b/notebooks/link_prediction/Overlap-Similarity.ipynb @@ -16,7 +16,8 @@ "| --------------|------------|------------------|-----------------|--------------------|\n", "| Brad Rees | 10/14/2019 | created | 0.08 | GV100, CUDA 10.0 |\n", "| | 08/16/2020 | upadted | 0.12 | GV100, CUDA 10.0 |\n", - "| | 08/05/2021 | tested / updated | 21.10 nightly | RTX 3090 CUDA 11.4 |\n" + "| | 08/05/2021 | tested / updated | 21.10 nightly | RTX 3090 CUDA 11.4 |\n", + "| Ralph Liu | 06/22/2022 | updated/tested | 22.08 | TV100, CUDA 11.5 |\n" ] }, { @@ -239,8 +240,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Read the CSV datafile using cuDF\n", - "data file is actually _tab_ separated, so we need to set the delimiter" + "### Import a Dataset Object" ] }, { @@ -249,10 +249,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Test file \n", - "datafile='../data/karate-data.csv'\n", - "\n", - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" + "from cugraph.experimental.datasets import karate\n", + "gdf = karate.get_edgelist(fetch=True)" ] }, { @@ -289,8 +287,8 @@ "outputs": [], "source": [ "# create a Graph \n", - "G = cugraph.Graph()\n", - "G.from_cudf_edgelist(gdf, source='src', destination='dst')" + "G = karate.get_graph()\n", + "G = G.to_undirected()" ] }, { @@ -582,9 +580,9 @@ ], "metadata": { "kernelspec": { - "display_name": "cugraph_dev", + "display_name": "Python 3.9.7 ('base')", "language": "python", - "name": "cugraph_dev" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -596,7 +594,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.9.7" + }, + "vscode": { + "interpreter": { + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" + } } }, "nbformat": 4, diff --git a/notebooks/sampling/RandomWalk.ipynb b/notebooks/sampling/RandomWalk.ipynb index afceff5378d..caacf909259 100644 --- a/notebooks/sampling/RandomWalk.ipynb +++ b/notebooks/sampling/RandomWalk.ipynb @@ -9,9 +9,10 @@ "In this notebook, we will compute the Random Walk from a set of seeds using cuGraph. \n", "\n", "\n", - "| Author Credit | Date | Update | cuGraph Version | Test Hardware |\n", - "| --------------|------------|--------------|-----------------|----------------|\n", - "| Brad Rees | 04/20/2021 | created | 0.19 | GV100, CUDA 11.0\n", + "| Author Credit | Date | Update | cuGraph Version | Test Hardware |\n", + "| --------------|------------|----------------|-----------------|----------------|\n", + "| Brad Rees | 04/20/2021 | created | 0.19 | GV100, CUDA 11.0\n", + "| Ralph Liu | 06/22/2022 | updated/tested | 22.08 | TV100, CUDA 11.5\n", "\n", "Currently NetworkX does not have a random walk function. There is code on StackOverflow that generats a random walk by getting a vertice and then randomly selection a neighbor and then repeating the process. " ] @@ -40,7 +41,10 @@ "source": [ "# Import the modules\n", "import cugraph\n", - "import cudf" + "import cudf\n", + "\n", + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate" ] }, { @@ -49,11 +53,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Read The Data\n", - "# Define the path to the test data \n", - "datafile='../data/karate-data.csv'\n", - "\n", - "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )" + "gdf = karate.get_edgelist(fetch=True)" ] }, { @@ -156,7 +156,7 @@ "metadata": {}, "source": [ "-----\n", - "Copyright (c) 2021, NVIDIA CORPORATION.\n", + "Copyright (c) 2022, NVIDIA CORPORATION.\n", "\n", "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n", "\n", @@ -166,9 +166,9 @@ ], "metadata": { "kernelspec": { - "display_name": "cugraph_dev", + "display_name": "Python 3.9.7 ('base')", "language": "python", - "name": "cugraph_dev" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -180,7 +180,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.9.7" + }, + "vscode": { + "interpreter": { + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" + } } }, "nbformat": 4, diff --git a/notebooks/structure/Renumber-2.ipynb b/notebooks/structure/Renumber-2.ipynb index a26becbb99f..03858b3e52a 100755 --- a/notebooks/structure/Renumber-2.ipynb +++ b/notebooks/structure/Renumber-2.ipynb @@ -20,13 +20,13 @@ "| Brad Rees | 08/13/2019 | created |\n", "| Brad Rees | 07/08/2020 | updated |\n", "| Ralph Liu | 06/01/2022 | docs & code change |\n", + "| | 06/22/2022 | update |\n", "\n", - "RAPIDS Versions: 0.13 \n", - "cuGraph Version: 22.06 \n", + "RAPIDS Versions: 22.08 \n", "\n", "Test Hardware\n", "\n", - "* GV100 32G, CUDA 11.5\n", + "* Tesla V100 32G, CUDA 11.5\n", "\n", "\n", "## Introduction\n", @@ -83,12 +83,9 @@ "metadata": {}, "outputs": [], "source": [ - "# Read the data\n", - "# the file contains an index column that will be ignored\n", - "\n", - "datafile='../data/cyber.csv'\n", - "\n", - "gdf = cudf.read_csv(datafile, delimiter=',', names=['idx','srcip','dstip'], dtype=['int32','str', 'str'], skiprows=1, usecols=['srcip', 'dstip'] )" + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import cyber\n", + "gdf = cyber.get_edgelist(fetch=True)" ] }, { @@ -104,6 +101,9 @@ "metadata": {}, "outputs": [], "source": [ + "# trim\n", + "gdf = gdf[1:]\n", + "\n", "# take a peek at the data\n", "gdf.head()" ] @@ -115,8 +115,8 @@ "outputs": [], "source": [ "# Since IP columns are strings, we first need to convert them to integers\n", - "gdf['src_ip'] = gdf['srcip'].str.ip2int()\n", - "gdf['dst_ip'] = gdf['dstip'].str.ip2int()" + "gdf['src_ip'] = gdf['src'].str.ip2int()\n", + "gdf['dst_ip'] = gdf['dst'].str.ip2int()" ] }, { @@ -225,7 +225,7 @@ "metadata": {}, "source": [ "___\n", - "Copyright (c) 2019-2020, NVIDIA CORPORATION.\n", + "Copyright (c) 2019-2022, NVIDIA CORPORATION.\n", "\n", "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n", "\n", @@ -235,11 +235,8 @@ } ], "metadata": { - "interpreter": { - "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" - }, "kernelspec": { - "display_name": "Python 3.6.9 64-bit", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -253,7 +250,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.9.7" + }, + "vscode": { + "interpreter": { + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" + } } }, "nbformat": 4, diff --git a/notebooks/structure/Renumber.ipynb b/notebooks/structure/Renumber.ipynb index 8c7b4e615eb..903ed9df389 100755 --- a/notebooks/structure/Renumber.ipynb +++ b/notebooks/structure/Renumber.ipynb @@ -16,13 +16,13 @@ "Notebook Credits\n", "* Original Authors: Chuck Hastings and Bradley Rees\n", "* Created: 08/13/2019\n", - "* Updated: 07/08/2020\n", + "* Updated: 06/22/2022\n", "\n", - "RAPIDS Versions: 0.15 \n", + "RAPIDS Versions: 22.08 \n", "\n", "Test Hardware\n", "\n", - "* GV100 32G, CUDA 10.2\n", + "* Tesla V100 32G, CUDA 11.5\n", "\n", "## Introduction\n", "\n", @@ -63,7 +63,7 @@ "import pandas as pd\n", "import numpy as np\n", "import networkx as nx\n", - "from cugraph.structure import NumberMap\n" + "from cugraph.structure import NumberMap" ] }, { @@ -331,7 +331,7 @@ "metadata": {}, "source": [ "___\n", - "Copyright (c) 2019-2020, NVIDIA CORPORATION.\n", + "Copyright (c) 2019-2022, NVIDIA CORPORATION.\n", "\n", "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n", "\n", @@ -342,7 +342,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -356,7 +356,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.9.7" + }, + "vscode": { + "interpreter": { + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" + } } }, "nbformat": 4, diff --git a/notebooks/structure/Symmetrize.ipynb b/notebooks/structure/Symmetrize.ipynb index 30b9d5dc618..f0c57baf070 100755 --- a/notebooks/structure/Symmetrize.ipynb +++ b/notebooks/structure/Symmetrize.ipynb @@ -11,13 +11,13 @@ "Notebook Credits\n", "* Original Authors: Bradley Rees and James Wyles\n", "* Created: 08/13/2019\n", - "* Updated: 03/02/2020\n", + "* Updated: 06/22/2022\n", "\n", - "RAPIDS Versions: 0.13 \n", + "RAPIDS Versions: 22.08 \n", "\n", "Test Hardware\n", "\n", - "* GV100 32G, CUDA 10.2\n", + "* Tesla V100 32G, CUDA 11.5\n", "\n", "\n", "## Introduction\n", @@ -80,9 +80,11 @@ "metadata": {}, "outputs": [], "source": [ - "# load the full symmetrized dataset for comparison\n", - "datafile='../data/karate-data.csv'\n", - "test_gdf = cudf.read_csv(datafile, names=[\"src\", \"dst\"], delimiter='\\t', dtype=[\"int32\", \"int32\"] )" + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate\n", + "\n", + "# This is the symmetrized dataset\n", + "test_gdf = karate.get_edgelist(fetch=True)" ] }, { @@ -162,7 +164,7 @@ "metadata": {}, "source": [ "---\n", - "Copyright (c) 2019-2020, NVIDIA CORPORATION.\n", + "Copyright (c) 2019-2022, NVIDIA CORPORATION.\n", "\n", "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n", "\n", @@ -173,9 +175,9 @@ ], "metadata": { "kernelspec": { - "display_name": "cugraph_dev", + "display_name": "Python 3.9.7 ('base')", "language": "python", - "name": "cugraph_dev" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -187,7 +189,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.9.7" + }, + "vscode": { + "interpreter": { + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" + } } }, "nbformat": 4, diff --git a/notebooks/traversal/BFS.ipynb b/notebooks/traversal/BFS.ipynb index ce768967eb9..b9409e2f821 100755 --- a/notebooks/traversal/BFS.ipynb +++ b/notebooks/traversal/BFS.ipynb @@ -10,13 +10,13 @@ "Notebook Credits\n", "* Original Authors: Bradley Rees and James Wyles\n", "* Feature available since 0.6\n", - "* Last Edit: 08/16/2020\n", + "* Last Edit: 06/22/2022\n", "\n", - "RAPIDS Versions: 0.14.0 \n", + "RAPIDS Versions: 22.08 \n", "\n", "Test Hardware\n", "\n", - "* GV100 32G, CUDA 10.0\n", + "* Tesla V100 32G, CUDA 11.5\n", "\n", "\n", "\n", @@ -94,7 +94,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Read the data using cuDF" + "# Create an Edgelist" ] }, { @@ -103,10 +103,10 @@ "metadata": {}, "outputs": [], "source": [ - "# Read the data file\n", - "datafile='../data/karate-data.csv'\n", + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate\n", "\n", - "gdf = cudf.read_csv(datafile, names=[\"src\", \"dst\"], delimiter='\\t', dtype=[\"int32\", \"int32\"] )" + "gdf = karate.get_edgelist(fetch=True)" ] }, { @@ -257,7 +257,7 @@ "metadata": {}, "source": [ "___\n", - "Copyright (c) 2019-2020, NVIDIA CORPORATION.\n", + "Copyright (c) 2019-2022, NVIDIA CORPORATION.\n", "\n", "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n", "\n", @@ -268,9 +268,9 @@ ], "metadata": { "kernelspec": { - "display_name": "cugraph_dev", + "display_name": "Python 3.9.7 ('base')", "language": "python", - "name": "cugraph_dev" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -282,7 +282,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.9.7" + }, + "vscode": { + "interpreter": { + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" + } } }, "nbformat": 4, diff --git a/notebooks/traversal/SSSP.ipynb b/notebooks/traversal/SSSP.ipynb index 3d0ce1c4234..a5e981f38c2 100755 --- a/notebooks/traversal/SSSP.ipynb +++ b/notebooks/traversal/SSSP.ipynb @@ -11,14 +11,14 @@ "Notebook Credits\n", "* Original Authors: Bradley Rees and James Wyles\n", "* available since release 0.6\n", - "* Last Edit: 08/16/2020\n", + "* Last Edit: 06/22/2022\n", "\n", "\n", - "RAPIDS Versions: 0.12.0 \n", + "RAPIDS Versions: 22.08 \n", "\n", "Test Hardware\n", "\n", - "* GV100 32G, CUDA 10.0\n", + "* Tesla V100 32G, CUDA 11.5\n", "\n", "\n", "\n", @@ -79,7 +79,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Read the data and adjust the vertex IDs" + "### Create an Edgelist" ] }, { @@ -88,17 +88,10 @@ "metadata": {}, "outputs": [], "source": [ - "# Test file - using the classic Karate club dataset. \n", - "datafile='../data/karate-data.csv'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "gdf = cudf.read_csv(datafile, names=[\"src\", \"dst\"], delimiter='\\t', dtype=[\"int32\", \"int32\"])" + "# Import a built-in dataset\n", + "from cugraph.experimental.datasets import karate\n", + "\n", + "gdf = karate.get_edgelist(fetch=True)" ] }, { @@ -173,7 +166,7 @@ "metadata": {}, "source": [ "___\n", - "Copyright (c) 2019-2020, NVIDIA CORPORATION.\n", + "Copyright (c) 2019-2022, NVIDIA CORPORATION.\n", "\n", "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n", "\n", @@ -184,7 +177,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3.9.7 ('base')", "language": "python", "name": "python3" }, @@ -198,7 +191,12 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.9.7" + }, + "vscode": { + "interpreter": { + "hash": "f708a36acfaef0acf74ccd43dfb58100269bf08fb79032a1e0a6f35bd9856f51" + } } }, "nbformat": 4,