Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Release

on:
push:
branches:
- main
workflow_dispatch:

jobs:

release:
runs-on: ubuntu-latest

environment:
name: pypi
permissions:
id-token: write
contents: write

steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Install uv
uses: astral-sh/setup-uv@v8.3.2
with:
python-version: "3.13"

- name: Build sdist and wheel
run: uv build

- name: Publish to PyPI
run: uv publish --trusted-publishing always

- name: Create release tag
# Adding a `--prerelease` flag in `gh release create` makes the release not
# show up as the latest stable release.
run: |
version="$(uv version --short)"
if uv run --no-project --with packaging python -c \
"import sys; from packaging.version import Version; sys.exit(0 if Version('${version}').is_prerelease else 1)"; then
prerelease="--prerelease"
else
prerelease=""
fi
gh release create "v${version}" --title "v${version}" --generate-notes ${prerelease}
env:
GH_TOKEN: ${{ github.token }}
22 changes: 6 additions & 16 deletions docs/tutorial/basic_concepts.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -899,9 +899,7 @@
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To simplify access to attribute values, the `Graph` class provides getter and setter functions that allow to access attribute values based on node identifiers. To access the feature `node_feature` of node `a`, we can write:"
]
"source": "To simplify access to attribute values, the `Graph` class provides getter and setter functions that allow to access attribute values based on node identifiers. To access the feature `node_class` of node `a`, we can write:"
},
{
"cell_type": "code",
Expand All @@ -919,9 +917,7 @@
"output_type": "execute_result"
}
],
"source": [
"g['node_class', 'a']"
]
"source": "g['node_class', 'a']"
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -1071,9 +1067,7 @@
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By passing the name of the attribute, we can use edge attributes in the creation of the adjacency matrix. To create a sparse, weighted adjacency matrix that uses the `edge_weight` attribute of our graph object we can simply write:"
]
"source": "By passing the name of the attribute, we can use edge attributes in the creation of the adjacency matrix. To create a dense, weighted adjacency matrix that uses the `edge_weight` attribute of our graph object we can simply write:"
},
{
"cell_type": "code",
Expand Down Expand Up @@ -1154,7 +1148,7 @@
"source": [
"It is often convenient, to coalesce multi-edges into weighted single-edges, i.e. in the example above we may prefer a graph where each edge occurs once in the edge index, but the edge `a->b` has a weight attribute of two, while the two other edges have one.\n",
"\n",
"In `pathpyG` we can do this by turning a graph into a weighted graph, which will coalesce edges and add an edge weight attribute that counts multi-edges in the original istance."
"In `pathpyG` we can do this by turning a graph into a weighted graph, which will coalesce edges and add an `edge_weight` attribute that counts multi-edges in the original instance."
]
},
{
Expand Down Expand Up @@ -1784,9 +1778,7 @@
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that the `pp.io.graph_to_df` function only includes `edge`-level data. To also include `node`-level attributes, you need to create a separate `DataFrame` for those attributes."
]
"source": "Note that the `pp.io.graph_to_df` function only includes `edge`-level data. To also include `node`-level attributes, you need to create a separate `DataFrame` for those attributes. A specially-named column `v` of this `DataFrame` should contain node labels (or use a column called `index` for integer node index)."
},
{
"cell_type": "code",
Expand Down Expand Up @@ -1837,9 +1829,7 @@
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Similarly, you can also add additional edge attributes from a `DataFrame` using the `add_edge_attributes` function:"
]
"source": "Similarly, you can also add additional edge attributes from a `DataFrame` using the `add_edge_attributes` function. Specially-named columns `v` and `w` of this `DataFrame` indicate the start-end of the edge respectively."
},
{
"cell_type": "code",
Expand Down
50 changes: 21 additions & 29 deletions docs/tutorial/manim_tutorial.ipynb

Large diffs are not rendered by default.

248 changes: 82 additions & 166 deletions docs/tutorial/netzschleuder.ipynb

Large diffs are not rendered by default.

104 changes: 31 additions & 73 deletions docs/tutorial/paths_higher_order.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"source": [
"## Motivation and Learning Objective\n",
"\n",
"While `pathpyG` is useful to handle and visualize static graphs - as the name suggests - its main advantage is that it facilitates the analysis of time series data that can be used to calculate **paths** in a graph. As we shall see in the following tutorial, there are various situations in which naturally have access to data on paths, including data on (random) walks or trajectories, traces of dynamical processes giving rise to node sequences or directed acyclic graphs, or time-respecting paths in temporal graphs. ``pathpyG` can be used to model patterns in such data based on higher-order De Bruijn graph models.\n",
"While `pathpyG` is useful to handle and visualize static graphs - as the name suggests - its main advantage is that it facilitates the analysis of time series data that can be used to calculate **paths** in a graph. As we shall see in the following tutorial, there are various situations in which we naturally have access to data on paths, including data on (random) walks or trajectories, traces of dynamical processes giving rise to node sequences or directed acyclic graphs, or time-respecting paths in temporal graphs. `pathpyG` can be used to model patterns in such data based on higher-order De Bruijn graph models.\n",
"\n",
"In this first unit, we will show how `pathpyG` supports to represent data on paths in graphs. Like graphs, such data are internally stored as tensors, which facilitates GPU-based operations to create higher-order De Bruijn graphs.\n",
"\n",
Expand Down Expand Up @@ -74,40 +74,13 @@
"}\n",
"</style>\n",
"\n",
"<div id = \"xe66a8cd586d945e4a34cd5f463f81193\"> </div>\n",
"<script charset=\"utf-8\" src=\"https://d3js.org/d3.v7.min.js\"></script>\n",
"<div id = \"x4d1798ec1e754fb882fb6e3c8a90a92e\"> </div>\n",
"<script charset=\"utf-8\">\n",
"// Load via requireJS if available (jupyter notebook environment)\n",
"try {\n",
" // Problem: require.config will raise an exception when called for the second time \n",
" require.config({\n",
" paths: {\n",
" d3: \"https://d3js.org/d3.v7.min.js\".replace(\".js\", \"\")\n",
" }\n",
" });\n",
" console.log(\"OKAY: requireJS was detected.\");\n",
"}\n",
"catch(err){\n",
" // a reference error indicates that requireJS does not exist. \n",
" // other errors may occur due to multiple calls to config\n",
" if (err instanceof ReferenceError){\n",
" console.log(\"WARNING: NO requireJS was detected!\");\n",
"\n",
" // Helper function that waits for d3js to be loaded\n",
" require = function require(symbols, callback) {\n",
" var ms = 10;\n",
" window.setTimeout(function(t) {\n",
" if (window[symbols[0]])\n",
" callback(window[symbols[0]]);\n",
" else \n",
" window.setTimeout(arguments.callee, ms);\n",
" }, ms);\n",
" }\n",
" }\n",
"};\n",
"require(['d3'], function(d3){ //START\n",
"function render_plot_98ee26c633294ef49ee96e3d81e93ca3() {\n",
" const d3 = window.d3;\n",
" if (!d3) { console.error('D3 not loaded'); return; }\n",
"const data = {\"nodes\": [{\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image\": null, \"uid\": \"a\"}, {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image\": null, \"uid\": \"b\"}, {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image\": null, \"uid\": \"c\"}, {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image\": null, \"uid\": \"d\"}, {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image\": null, \"uid\": \"e\"}], \"edges\": [{\"color\": \"#4c707b\", \"size\": 2, \"opacity\": 0.5, \"image\": null, \"uid\": \"a-c\", \"source\": \"a\", \"target\": \"c\"}, {\"color\": \"#4c707b\", \"size\": 2, \"opacity\": 0.5, \"image\": null, \"uid\": \"b-c\", \"source\": \"b\", \"target\": \"c\"}, {\"color\": \"#4c707b\", \"size\": 2, \"opacity\": 0.5, \"image\": null, \"uid\": \"c-d\", \"source\": \"c\", \"target\": \"d\"}, {\"color\": \"#4c707b\", \"size\": 2, \"opacity\": 0.5, \"image\": null, \"uid\": \"c-e\", \"source\": \"c\", \"target\": \"e\"}]}\n",
"const config = {\"default_backend\": \"d3js\", \"cmap\": \"cividis\", \"layout\": null, \"width\": 453.54330708661416, \"height\": 453.54330708661416, \"latex_class_options\": \"\", \"margin\": 0.1, \"curvature\": 0.25, \"layout_window_size\": [-1, -1], \"delta\": 1000, \"separator\": \"->\", \"orientation\": \"down\", \"node\": {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image_padding\": 5}, \"edge\": {\"color\": \"#4c707b\", \"size\": 2, \"opacity\": 0.5}, \"directed\": true, \"curved\": true, \"simulation\": true, \"d3js_local\": false, \"selector\": \"#xe66a8cd586d945e4a34cd5f463f81193\", \"show_labels\": true}\n",
"const config = {\"default_backend\": \"d3js\", \"cmap\": \"cividis\", \"layout\": null, \"width\": 453.54330708661416, \"height\": 453.54330708661416, \"latex_class_options\": \"\", \"margin\": 0.1, \"curvature\": 0.25, \"layout_window_size\": [-1, -1], \"delta\": 1000, \"separator\": \"->\", \"orientation\": \"down\", \"node\": {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image_padding\": 5}, \"edge\": {\"color\": \"#4c707b\", \"size\": 2, \"opacity\": 0.5}, \"directed\": true, \"curved\": true, \"simulation\": true, \"d3js_local\": false, \"selector\": \"#x4d1798ec1e754fb882fb6e3c8a90a92e\", \"show_labels\": true}\n",
"console.log('static Network Template');\n",
"// Useful pages\n",
"// ------------\n",
Expand Down Expand Up @@ -569,9 +542,15 @@
"// Draw the network.\n",
"drawStaticNetwork();\n",
"\n",
"}); //END\n",
"}; //END of Render Function\n",
"\n",
"</script>"
"</script>\n",
" <script type=\"module\">\n",
" import * as d3 from \"https://cdn.jsdelivr.net/npm/d3@7/+esm\";\n",
" window.d3 = d3;\n",
" render_plot_98ee26c633294ef49ee96e3d81e93ca3();\n",
" </script>\n",
" "
]
},
"metadata": {},
Expand Down Expand Up @@ -744,7 +723,7 @@
"source": [
"We can actually see a collection of walks as a higher-order generalization of the usual way to define graphs as a collection of dyadic edges (which are simply walks of length one). From this point of view, a standard static (weighted) graph is simply a first-order model of node sequences, which only considers the frequency at which edges are traversed. \n",
"\n",
"To generate such a first-order model, we can use the class `MultiOderModel` and use the first-layer of the model, which is simply a weighted static graph where edge weights count the number of times each edge is traversed by a path. We will explain the class `MultiOrderModel`, which generalizes this concept to higher-order graph models for any order $k$ in a moment. For now, we can just use it to generate a first-order weighted graph as follows.\n",
"To generate such a first-order model, we can use the class `MultiOrderModel` and use the first-layer of the model, which is simply a weighted static graph where edge weights count the number of times each edge is traversed by a path. We will explain the class `MultiOrderModel`, which generalizes this concept to higher-order graph models for any order $k$ in a moment. For now, we can just use it to generate a first-order weighted graph as follows.\n",
"\n",
"The generated graph is again based on a `pyG.Data` object that contains an edge_index and edge weights. As we can see, for the example above the edge_index is just a concatenation of the edge indices of individual walks, where the node indices have been mapped to the correct nodes."
]
Expand Down Expand Up @@ -774,40 +753,13 @@
"}\n",
"</style>\n",
"\n",
"<div id = \"x14e449c563864e40af47755664569185\"> </div>\n",
"<script charset=\"utf-8\" src=\"https://d3js.org/d3.v7.min.js\"></script>\n",
"<div id = \"x8823bfd00c7f473980bc41d9ab77c8f0\"> </div>\n",
"<script charset=\"utf-8\">\n",
"// Load via requireJS if available (jupyter notebook environment)\n",
"try {\n",
" // Problem: require.config will raise an exception when called for the second time \n",
" require.config({\n",
" paths: {\n",
" d3: \"https://d3js.org/d3.v7.min.js\".replace(\".js\", \"\")\n",
" }\n",
" });\n",
" console.log(\"OKAY: requireJS was detected.\");\n",
"}\n",
"catch(err){\n",
" // a reference error indicates that requireJS does not exist. \n",
" // other errors may occur due to multiple calls to config\n",
" if (err instanceof ReferenceError){\n",
" console.log(\"WARNING: NO requireJS was detected!\");\n",
"\n",
" // Helper function that waits for d3js to be loaded\n",
" require = function require(symbols, callback) {\n",
" var ms = 10;\n",
" window.setTimeout(function(t) {\n",
" if (window[symbols[0]])\n",
" callback(window[symbols[0]]);\n",
" else \n",
" window.setTimeout(arguments.callee, ms);\n",
" }, ms);\n",
" }\n",
" }\n",
"};\n",
"require(['d3'], function(d3){ //START\n",
"function render_plot_d2a23a5803dc4aa480b3975314d13647() {\n",
" const d3 = window.d3;\n",
" if (!d3) { console.error('D3 not loaded'); return; }\n",
"const data = {\"nodes\": [{\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image\": null, \"uid\": \"a\"}, {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image\": null, \"uid\": \"b\"}, {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image\": null, \"uid\": \"c\"}, {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image\": null, \"uid\": \"d\"}, {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image\": null, \"uid\": \"e\"}], \"edges\": [{\"color\": \"#4c707b\", \"size\": 4.0, \"opacity\": 0.5, \"image\": null, \"uid\": \"a-c\", \"source\": \"a\", \"target\": \"c\"}, {\"color\": \"#4c707b\", \"size\": 4.0, \"opacity\": 0.5, \"image\": null, \"uid\": \"b-c\", \"source\": \"b\", \"target\": \"c\"}, {\"color\": \"#4c707b\", \"size\": 4.0, \"opacity\": 0.5, \"image\": null, \"uid\": \"c-d\", \"source\": \"c\", \"target\": \"d\"}, {\"color\": \"#4c707b\", \"size\": 4.0, \"opacity\": 0.5, \"image\": null, \"uid\": \"c-e\", \"source\": \"c\", \"target\": \"e\"}]}\n",
"const config = {\"default_backend\": \"d3js\", \"cmap\": \"cividis\", \"layout\": null, \"width\": 453.54330708661416, \"height\": 453.54330708661416, \"latex_class_options\": \"\", \"margin\": 0.1, \"curvature\": 0.25, \"layout_window_size\": [-1, -1], \"delta\": 1000, \"separator\": \"->\", \"orientation\": \"down\", \"node\": {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image_padding\": 5}, \"edge\": {\"color\": \"#4c707b\", \"size\": 2, \"opacity\": 0.5}, \"directed\": true, \"curved\": true, \"simulation\": true, \"d3js_local\": false, \"selector\": \"#x14e449c563864e40af47755664569185\", \"show_labels\": true}\n",
"const config = {\"default_backend\": \"d3js\", \"cmap\": \"cividis\", \"layout\": null, \"width\": 453.54330708661416, \"height\": 453.54330708661416, \"latex_class_options\": \"\", \"margin\": 0.1, \"curvature\": 0.25, \"layout_window_size\": [-1, -1], \"delta\": 1000, \"separator\": \"->\", \"orientation\": \"down\", \"node\": {\"color\": \"#244a5c\", \"size\": 15, \"opacity\": 0.75, \"image_padding\": 5}, \"edge\": {\"color\": \"#4c707b\", \"size\": 2, \"opacity\": 0.5}, \"directed\": true, \"curved\": true, \"simulation\": true, \"d3js_local\": false, \"selector\": \"#x8823bfd00c7f473980bc41d9ab77c8f0\", \"show_labels\": true}\n",
"console.log('static Network Template');\n",
"// Useful pages\n",
"// ------------\n",
Expand Down Expand Up @@ -1269,9 +1221,15 @@
"// Draw the network.\n",
"drawStaticNetwork();\n",
"\n",
"}); //END\n",
"}; //END of Render Function\n",
"\n",
"</script>"
"</script>\n",
" <script type=\"module\">\n",
" import * as d3 from \"https://cdn.jsdelivr.net/npm/d3@7/+esm\";\n",
" window.d3 = d3;\n",
" render_plot_d2a23a5803dc4aa480b3975314d13647();\n",
" </script>\n",
" "
]
},
"metadata": {},
Expand All @@ -1290,7 +1248,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Why are data on paths and walks interesting in the first place. The answer is that they provide information on the **causal topology of complex systems**, i.e. which nodes can possibly causally influence each other via paths that follow the arrow of time. This information is lost if we were to split paths into an (unordered) collection of dyadyic interactions between pairs of nodes, i.e. if we were to only onsider links.\n",
"Why are data on paths and walks interesting in the first place? The answer is that they provide information on the **causal topology of complex systems**, i.e. which nodes can possibly causally influence each other via paths that follow the arrow of time. This information is lost if we were to split paths into an (unordered) collection of dyadic interactions between pairs of nodes, i.e. if we were to only onsider links.\n",
"\n",
"To illustrate this, let us assume that the four walks above tell us which paths information (or whatever you may be interested in) can take in the simple graph above. That is, we observe something moving from `a` via `c` to `d` and from `b` via `c` to `e`, and each of those events occur four times. However, we never observed that something moving from `a` to `c` ended up in `d`. And neither did we observe that something moving from `b` to `c` ended up in `e`. This means that - assuming that we completely observed all walks or paths - there is no way that `a` can causally influence `e` or that `b` could causally influence `d` via the center node `c`. Note that this is not what we would assume if we consider possible paths in the topology of the underlying graph, where paths of length two exist between all four pairs of nodes (`a`, `d`), (`a`, `e`), (`b`, `d`), (`b`, `e`).\n",
"\n",
Expand Down Expand Up @@ -4428,7 +4386,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "pathpyg",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
Expand All @@ -4442,7 +4400,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.18"
"version": "3.13.12"
}
},
"nbformat": 4,
Expand Down
Loading
Loading