diff --git a/configs/datasets/annulus.yaml b/configs/datasets/annulus.yaml new file mode 100644 index 00000000..23e8bfb5 --- /dev/null +++ b/configs/datasets/annulus.yaml @@ -0,0 +1,13 @@ +data_domain: pointcloud +data_type: toy_dataset +data_name: annulus +data_dir: datasets/${data_domain}/${data_type}/${data_name} + +# Dataset parameters +task: classification +loss_type: cross_entropy +monitor_metric: accuracy +num_features: 10 +num_classes: 3 +num_points: 1000 +dim: 2 \ No newline at end of file diff --git a/configs/datasets/random_pointcloud.yaml b/configs/datasets/random_pointcloud.yaml new file mode 100644 index 00000000..0222a387 --- /dev/null +++ b/configs/datasets/random_pointcloud.yaml @@ -0,0 +1,13 @@ +data_domain: pointcloud +data_type: toy_dataset +data_name: random_pointcloud +data_dir: datasets/${data_domain}/${data_type}/${data_name} + +# Dataset parameters +task: classification +loss_type: cross_entropy +monitor_metric: accuracy +num_features: 10 +num_classes: 3 +num_points: 50 +dim: 2 \ No newline at end of file diff --git a/configs/datasets/stanford_bunny.yaml b/configs/datasets/stanford_bunny.yaml new file mode 100644 index 00000000..6f1a3f07 --- /dev/null +++ b/configs/datasets/stanford_bunny.yaml @@ -0,0 +1,11 @@ +data_domain: pointcloud +data_type: toy_dataset +data_name: stanford_bunny +data_dir: datasets/${data_domain}/${data_type}/${data_name} + +# Dataset parameters +num_classes: 1 +task: regression +loss_type: mse +monitor_metric: mae +task_level: graph \ No newline at end of file diff --git a/configs/transforms/liftings/pointcloud2graph/cover_lifting.yaml b/configs/transforms/liftings/pointcloud2graph/cover_lifting.yaml new file mode 100644 index 00000000..ae4db7a5 --- /dev/null +++ b/configs/transforms/liftings/pointcloud2graph/cover_lifting.yaml @@ -0,0 +1,3 @@ +transform_type: 'lifting' +transform_name: "CoverLifting" +feature_lifting: ProjectionSum \ No newline at end of file diff --git a/modules/data/load/loaders.py b/modules/data/load/loaders.py index 8ccafb11..c5e48e3d 100755 --- a/modules/data/load/loaders.py +++ b/modules/data/load/loaders.py @@ -12,6 +12,7 @@ load_cell_complex_dataset, load_hypergraph_pickle_dataset, load_manual_graph, + load_pointcloud_dataset, load_simplicial_dataset, ) @@ -204,3 +205,36 @@ def load( torch_geometric.data.Dataset object containing the loaded data. """ return load_hypergraph_pickle_dataset(self.parameters) + + +class PointCloudLoader(AbstractLoader): + r"""Loader for point cloud datasets. + Parameters + ---------- + parameters : DictConfig + Configuration parameters. + """ + + def __init__(self, parameters: DictConfig): + super().__init__(parameters) + self.parameters = parameters + + def load( + self, + ) -> torch_geometric.data.Dataset: + r"""Load point cloud dataset. + Parameters + ---------- + None + Returns + ------- + torch_geometric.data.Dataset + torch_geometric.data.Dataset object containing the loaded data. + """ + # Define the path to the data directory + root_folder = rootutils.find_root() + self.data_dir = os.path.join(root_folder, self.parameters["data_dir"]) + + data = load_pointcloud_dataset(self.parameters) + print(data, data[0]) + return load_pointcloud_dataset(self.parameters) diff --git a/modules/data/utils/utils.py b/modules/data/utils/utils.py index 93ab5021..fbb39a1a 100755 --- a/modules/data/utils/utils.py +++ b/modules/data/utils/utils.py @@ -5,13 +5,17 @@ import networkx as nx import numpy as np import omegaconf +import rootutils import toponetx.datasets.graph as graph import torch import torch_geometric +from gudhi.datasets.remote import fetch_bunny from topomodelx.utils.sparse import from_sparse from torch_geometric.data import Data from torch_sparse import coalesce +from modules.data.utils.custom_dataset import CustomDataset + def get_complex_connectivity(complex, max_rank, signed=False): r"""Gets the connectivity matrices for the complex. @@ -50,16 +54,16 @@ def get_complex_connectivity(complex, max_rank, signed=False): ) except ValueError: # noqa: PERF203 if connectivity_info == "incidence": - connectivity[f"{connectivity_info}_{rank_idx}"] = ( - generate_zero_sparse_connectivity( - m=practical_shape[rank_idx - 1], n=practical_shape[rank_idx] - ) + connectivity[ + f"{connectivity_info}_{rank_idx}" + ] = generate_zero_sparse_connectivity( + m=practical_shape[rank_idx - 1], n=practical_shape[rank_idx] ) else: - connectivity[f"{connectivity_info}_{rank_idx}"] = ( - generate_zero_sparse_connectivity( - m=practical_shape[rank_idx], n=practical_shape[rank_idx] - ) + connectivity[ + f"{connectivity_info}_{rank_idx}" + ] = generate_zero_sparse_connectivity( + m=practical_shape[rank_idx], n=practical_shape[rank_idx] ) connectivity["shape"] = practical_shape return connectivity @@ -372,6 +376,89 @@ def get_TUDataset_pyg(cfg): return [data for data in dataset] +def load_pointcloud_dataset(cfg): + r"""Loads point cloud datasets. + Parameters + ---------- + cfg : DictConfig + Configuration parameters. + Returns + ------- + torch_geometric.data.Data + Point cloud dataset. + """ + root_folder = rootutils.find_root() + data_dir = osp.join(root_folder, cfg["data_dir"]) + + if cfg["data_name"] == "random_pointcloud": + num_points, dim = cfg["num_points"], cfg["dim"] + pos = torch.rand((num_points, dim)) + elif cfg["data_name"] == "annulus": + num_points, dim = cfg["num_points"], cfg["dim"] + pos = sample_annulus(dim, num_points) + elif cfg["data_name"] == "stanford_bunny": + pos = fetch_bunny( + file_path=osp.join(data_dir, "stanford_bunny.npy"), + accept_license=False, + ) + pos = torch.tensor(pos) + + return CustomDataset( + [ + torch_geometric.data.Data( + pos=pos, + ) + ], + data_dir, + ) + + +def sample_annulus( + dimension: int, num_points: int, inner_radius: float = 0.8, outer_radius: float = 1 +): + """Sample points from annulus of the given dimension. + + Parameters + ---------- + dimension : int + Dimension + num_points : int + Size of sample + inner_radius : float, optional + Inner radius, by default 0.8 + outer_radius : float, optional + Outer radius, by default 1 + + Returns + ------- + torch.Tensor + Tensor of sampled points + """ + n = 0 + P = np.array([[0.0] * dimension] * num_points) + + rng = np.random.default_rng() + while n < num_points: + p = rng.uniform(-outer_radius, outer_radius, dimension) + if np.linalg.norm(p) > outer_radius or np.linalg.norm(p) < inner_radius: + continue + P[n] = p + n = n + 1 + return torch.tensor(P) + + +def load_annulus(): + """Loads 2D annulus point cloud. + + Returns + ------- + torch_geometric.data.Data + Point cloud data. + """ + pos = sample_annulus(2, 1000) + return torch_geometric.data.Data(pos=pos) + + def ensure_serializable(obj): r"""Ensures that the object is serializable. diff --git a/modules/transforms/data_transform.py b/modules/transforms/data_transform.py index 59253ecf..b4c69229 100755 --- a/modules/transforms/data_transform.py +++ b/modules/transforms/data_transform.py @@ -15,6 +15,7 @@ from modules.transforms.liftings.graph2simplicial.clique_lifting import ( SimplicialCliqueLifting, ) +from modules.transforms.liftings.pointcloud2graph.cover_lifting import CoverLifting TRANSFORMS = { # Graph -> Hypergraph @@ -23,6 +24,8 @@ "SimplicialCliqueLifting": SimplicialCliqueLifting, # Graph -> Cell Complex "CellCycleLifting": CellCycleLifting, + # Point Cloud -> Graph + "CoverLifting": CoverLifting, # Feature Liftings "ProjectionSum": ProjectionSum, # Data Manipulations diff --git a/modules/transforms/liftings/lifting.py b/modules/transforms/liftings/lifting.py index ddb72781..7066b3d0 100644 --- a/modules/transforms/liftings/lifting.py +++ b/modules/transforms/liftings/lifting.py @@ -61,6 +61,7 @@ def forward(self, data: torch_geometric.data.Data) -> torch_geometric.data.Data: initial_data = data.to_dict() lifted_topology = self.lift_topology(data) lifted_topology = self.feature_lifting(lifted_topology) + return torch_geometric.data.Data(**initial_data, **lifted_topology) @@ -118,9 +119,11 @@ def _generate_graph_from_data(self, data: torch_geometric.data.Data) -> nx.Graph # In case edge features are given, assign features to every edge edge_index, edge_attr = ( data.edge_index, - data.edge_attr - if is_undirected(data.edge_index, data.edge_attr) - else to_undirected(data.edge_index, data.edge_attr), + ( + data.edge_attr + if is_undirected(data.edge_index, data.edge_attr) + else to_undirected(data.edge_index, data.edge_attr) + ), ) edges = [ (i.item(), j.item(), dict(features=edge_attr[edge_idx], dim=1)) diff --git a/modules/transforms/liftings/pointcloud2graph/cover_lifting.py b/modules/transforms/liftings/pointcloud2graph/cover_lifting.py new file mode 100644 index 00000000..112a80e2 --- /dev/null +++ b/modules/transforms/liftings/pointcloud2graph/cover_lifting.py @@ -0,0 +1,223 @@ +from functools import partial + +import gudhi +import gudhi.cover_complex +import numpy as np +import statsmodels.stats.multitest as mt +import torch +import torch_geometric +from statsmodels.distributions.empirical_distribution import ECDF +from torch_geometric.utils.convert import from_networkx + +from modules.transforms.liftings.pointcloud2graph.base import PointCloud2GraphLifting + +rng = np.random.default_rng() + + +def persistent_homology(points: torch.Tensor, subcomplex_inds: list[int] | None = None): + """Calculate (relative) persistent homology using Alpha complex. + + Parameters + ---------- + points : torch.Tensor + Set of points. + subcomplex_inds : list[int] | None, optional + Points on the boundary (subcomplex), by default None + + Returns + ------- + torch.Tensor + Persistence diagram + """ + st = gudhi.AlphaComplex(points=points).create_simplex_tree() + + if subcomplex_inds is not None: + subcomplex = [ + simplex + for simplex, _ in st.get_simplices() + if all(x in subcomplex_inds for x in simplex) + ] + + new_vertex = st.num_vertices() + st.insert([new_vertex], 0) + for simplex in subcomplex: + st.insert([*simplex, new_vertex], st.filtration(simplex)) + + persistence = st.persistence() + + return np.array( + [(birth, death) for (dim, (birth, death)) in persistence if dim == 1] + ) + + +def transform_diagram(diagram: torch.Tensor): + """Transform the diagram to a list of pi values (birth / death). + + Parameters + ---------- + diagram : torch.Tensor + Persistence diagram + + Returns + ------- + torch.Tensor + Tensor of pi values. + """ + + b, d = diagram[:, 0], diagram[:, 1] + pi = d / b + return np.log(pi) + + +def get_empirical_distribution(dim: int): + """Generates empirical distribution of pi values for random pointcloud in R^{dim} + + Parameters + ---------- + dim : int + Dimension + + Returns + ------- + ECDF + CDF of the distribution. + """ + random_pc = rng.uniform(size=(10000, dim)) + dgm_rand = persistent_homology(random_pc) + return ECDF(transform_diagram(dgm_rand)) + + +def test_weak_universality(emp_cdf: ECDF, diagram, alpha: float = 0.05): + """Test cycles for significance using weak universality. + See: Bobrowski, O., Skraba, P. A universal null-distribution for topological data analysis. Sci Rep 13, 12274 (2023). + + Parameters + ---------- + emp_cdf : ECDF + Emperical CDF of pi values of random points. + diagram : _type_ + Persistence diagram + alpha : float, optional + p-value, by default 0.05 + + Returns + ------- + int + Number of significant cycles. + """ + pvals = 1 - emp_cdf(transform_diagram(diagram)) + is_significant, _, _, _ = mt.multipletests(pvals, alpha=alpha, method="bonferroni") + return np.sum(is_significant) + + +def sample_points(points: torch.Tensor, n: int = 300): + """Sample n random points. + + Parameters + ---------- + points : torch.Tensor + Points + n : int, optional + Size of sample, by default 300 + + Returns + ------- + torch.Tensor + Sample + """ + return points[rng.choice(points.shape[0], min(n, points.shape[0]), replace=False)] + + +class CoverLifting(PointCloud2GraphLifting): + r"""Lifts point cloud data to graph by creating its k-NN graph + + Parameters + ---------- + **kwargs : optional + Additional arguments for the class + """ + + def __init__( + self, + ambient_dim: int = 2, + **kwargs, + ): + super().__init__(**kwargs) + + self.cover_complex = gudhi.cover_complex.MapperComplex( + input_type="point cloud", + min_points_per_node=0, + clustering=None, + N=100, + beta=0.0, + C=10, + filter_bnds=None, + resolutions=None, + gains=None, + ) + + self.test_fn = partial( + test_weak_universality, get_empirical_distribution(ambient_dim) + ) + + def lift_topology(self, data: torch_geometric.data.Data) -> dict: + r"""Lifts a point cloud dataset to a graph by constructing its k-NN graph. + + Parameters + ---------- + data : torch_geometric.data.Data + The input data to be lifted + + Returns + ------- + dict + The lifted topology + """ + points = data.pos + + # use height function as the filter + height = points[:, -1] + _ = self.cover_complex.fit(data.pos, filters=height, colors=height) + + graph = self.cover_complex.get_networkx() + + removed_edges = [] + for u, v in graph.edges(): + u_inds = set([int(x) for x in self.cover_complex.node_info_[u]["indices"]]) + v_inds = set([int(x) for x in self.cover_complex.node_info_[v]["indices"]]) + + interior = sample_points(points[list(u_inds & v_inds)]) + + u_boundary = sample_points(points[list(u_inds - v_inds)]) + v_boundary = sample_points(points[list(v_inds - u_inds)]) + + remove_edge = True + if min(len(u_boundary), len(v_boundary)) == 0: + remove_edge = False + elif len(interior) > 0: + # number of significant cycles + num_cycles = self.test_fn(persistent_homology(interior)) + + # number of significant relative cycles + x = np.vstack([interior, u_boundary, v_boundary]) + num_relative_cycles = self.test_fn( + persistent_homology( + x, subcomplex_inds=np.arange(interior.shape[0], x.shape[0]) + ) + ) + + if num_relative_cycles > num_cycles: + remove_edge = False + + if remove_edge: + removed_edges.append((u, v)) + + graph.remove_edges_from(removed_edges) + + graph_data = from_networkx(graph) + + return { + "num_nodes": graph_data.num_nodes, + "edge_index": graph_data.edge_index, + "x": torch.ones((graph_data.num_nodes, 1)), + } diff --git a/pyproject.toml b/pyproject.toml index af67ad7c..ee9b9dab 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ dependencies=[ "toponetx @ git+https://github.com/pyt-team/TopoNetX.git", "topomodelx @ git+https://github.com/pyt-team/TopoModelX.git", "topoembedx @ git+https://github.com/pyt-team/TopoEmbedX.git", + "statsmodels", ] diff --git a/test/transforms/liftings/pointcloud2graph/test_cover_lifting.py b/test/transforms/liftings/pointcloud2graph/test_cover_lifting.py new file mode 100644 index 00000000..d1b2d66f --- /dev/null +++ b/test/transforms/liftings/pointcloud2graph/test_cover_lifting.py @@ -0,0 +1,26 @@ +"""Test the message passing module.""" + +import networkx as nx +from torch_geometric.utils.convert import to_networkx + +from modules.data.utils.utils import load_annulus +from modules.transforms.liftings.pointcloud2graph.cover_lifting import CoverLifting + + +class TestCoverLifting: + """Test the SimplicialCliqueLifting class.""" + + def setup_method(self): + # Load the point cloud + self.data = load_annulus() + + # Initialise the CoverLifting class + self.lifting = CoverLifting() + + def test_lift_topology(self): + """Test the lift_topology method.""" + # Test the lift_topology method + lifted_data = self.lifting(self.data) + + g = to_networkx(lifted_data, to_undirected=True) + nx.find_cycle(g) diff --git a/tutorials/pointcloud2graph/cover_lifting.ipynb b/tutorials/pointcloud2graph/cover_lifting.ipynb new file mode 100644 index 00000000..363eca24 --- /dev/null +++ b/tutorials/pointcloud2graph/cover_lifting.ipynb @@ -0,0 +1,306 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Point Cloud-to-Graph Cover Lifting Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "***\n", + "This notebook shows how to import a dataset, with the desired lifting, and how to run a neural network using the loaded data.\n", + "\n", + "The notebook is divided into sections:\n", + "\n", + "- [Loading the dataset](#loading-the-dataset) loads the config files for the data and the desired tranformation, createsa a dataset object and visualizes it.\n", + "- [Loading and applying the lifting](#loading-and-applying-the-lifting) defines a simple neural network to test that the lifting creates the expected incidence matrices.\n", + "- [Create and run a simplicial nn model](#create-and-run-a-simplicial-nn-model) simply runs a forward pass of the model to check that everything is working as expected.\n", + "\n", + "***\n", + "***\n", + "Note that for simplicity the notebook is setup to use a simple point cloud. However, there is a set of available datasets that you can play with.\n", + "\n", + "To switch to one of the available datasets, simply change the *dataset_name* variable in [Dataset config](#dataset-config) to one of the following names:\n", + "\n", + "* random_pointcloud\n", + "* annulus\n", + "* stanford_bunny\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Imports and utilities" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# With this cell any imported module is reloaded before each cell execution\n", + "%load_ext autoreload\n", + "%autoreload 2\n", + "from modules.data.load.loaders import PointCloudLoader\n", + "from modules.data.preprocess.preprocessor import PreProcessor\n", + "from modules.utils.utils import (\n", + " describe_data,\n", + " load_dataset_config,\n", + " load_transform_config,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dataset from here: https://github.com/pyt-team/challenge-icml-2024/pull/34/files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Loading the Dataset" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we just need to spicify the name of the available dataset that we want to load. First, the dataset config is read from the corresponding yaml file (located at `/configs/datasets/` directory), and then the data is loaded via the implemented `Loaders`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Dataset configuration for annulus:\n", + "\n", + "{'data_domain': 'pointcloud',\n", + " 'data_type': 'toy_dataset',\n", + " 'data_name': 'annulus',\n", + " 'data_dir': 'datasets/pointcloud/toy_dataset/annulus',\n", + " 'task': 'classification',\n", + " 'loss_type': 'cross_entropy',\n", + " 'monitor_metric': 'accuracy',\n", + " 'num_features': 10,\n", + " 'num_classes': 3,\n", + " 'num_points': 1000,\n", + " 'dim': 2}\n" + ] + } + ], + "source": [ + "dataset_name = \"annulus\"\n", + "dataset_config = load_dataset_config(dataset_name)\n", + "loader = PointCloudLoader(dataset_config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can then access to the data through the `load()`method:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CustomDataset() Data(pos=[1000, 2])\n" + ] + } + ], + "source": [ + "dataset = loader.load()\n", + "# describe_data(dataset)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Loading and Applying the Lifting" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this section we will instantiate the lifting we want to apply to the data. For this example the cover lifting was chosen.\n", + "\n", + "The algorithm initially constructs the Mapper graph from the given point cloud. Each vertex $v$ in the graph is associated with a set of points $\\phi(v)$, and two vertices $(u, v)$ are connected if their point sets intersect. Our connectivity test determines whether there is significant evidence for the connectedness of $\\phi(u)$ and $\\phi(v)$.\n", + "\n", + "We formulate the connectivity test using a recently observed universal property of persistent diagrams [[1]](https://doi.org/10.1038/s41598-023-37842-2), which enables us to detect statistically significant homological cycles. The test employs \"Weak Universality\" and calculates the number of significant relative cycles in $H_1(\\phi(u) \\cup \\phi(v), \\phi(u) \\setminus \\phi(v) \\cup \\phi(v) \\setminus \\phi(u))$ as well as the number of significant cycles in $H_1(\\phi(u) \\cap \\phi(v)$. The emergence of new relative cycles confirms the connectivity between $u$ and $v$.\n", + "\n", + "***\n", + "[[1]](https://doi.org/10.1038/s41598-023-37842-2) Bobrowski, O., Skraba, P. A universal null-distribution for topological data analysis. Sci Rep 13, 12274 (2023). \n", + "***" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Transform configuration for pointcloud2graph/cover_lifting:\n", + "\n", + "{'transform_type': 'lifting',\n", + " 'transform_name': 'CoverLifting',\n", + " 'feature_lifting': 'ProjectionSum'}\n" + ] + } + ], + "source": [ + "# Define transformation type and id\n", + "transform_type = \"liftings\"\n", + "# If the transform is a topological lifting, it should include both the type of the lifting and the identifier\n", + "transform_id = \"pointcloud2graph/cover_lifting\"\n", + "\n", + "# Read yaml file\n", + "transform_config = {\n", + " \"lifting\": load_transform_config(transform_type, transform_id)\n", + " # other transforms (e.g. data manipulations, feature liftings) can be added here\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We than apply the transform via our `PreProcesor`:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Transform parameters are the same, using existing data_dir: /home/patrik/Work/icml2024/challenge-icml-2024/datasets/pointcloud/toy_dataset/annulus/lifting/1967311950\n", + "\n", + "Dataset only contains 1 sample:\n" + ] + }, + { + "data": { + "image/png": "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cnK45ZZbOProo0lJScG2bcaPH9+o+upaWKox3xsRqUlnDkQacPbZZ7N+/fo6F/T573//y4YNGzj77LObtf++ffti2zbr16+vcfu6deuatb+6LFiwgNNPP51//OMfXHTRRYwdO5bRo0dTWFjYrP2dffbZVFRUNGmNh7oUFBSwbNkyZs6cye23387555/PmDFj6p0a+u2339b4et26ddi2Tb9+/fa7HpF4p3Ag0oBbbrmFxMRErrzyyn2m1eXn5zNjxgySkpK45ZZbmrX/cePGATB37twat//lL39pXsF1cLvd+3xanj9/Plu2bGnW/n7xi1/QvXt3brjhBtauXbvP/U35ZF716X7vx8yePbvOx8yZM6fG11XfrzPPPLPRzysitdOwgkgDDjnkEP75z3/yk5/8hCOPPJJp06bRv39/NmzYwD/+8Q927drFc889V6PprSmGDh3KBRdcwOzZs8nLy6ueylh1wG3udRj2dvbZZ3PHHXcwZcoUhg8fzhdffMGzzz5b76fz+qSlpfHiiy9yzjnncNRRR3HRRRdx/PHH4/V62bRpE/PnzwdqH/PfW2pqKqeccgr33nsvoVCIXr16sWTJkn3Opuxp/fr1nHvuuYwfP56VK1fyzDPPcMkll3DUUUc16/WIyI8UDkQaISsri8GDB3PPPfdUB4L09HROP/10fvOb33DEEUfs1/6feuopevTowXPPPceLL77I6NGjef755xk0aBB+v79FXsNvfvMbysrK+Ne//sXzzz/Psccey+uvv87MmTObvc9hw4bx5Zdfcv/99/P666/z/PPPY9s2vXr14uSTT+bRRx+tc32Ivf3rX//i2muvZc6cORhjGDt2LIsWLapz/YXnn3+e3/3ud8ycOROPx8M111zDfffd1+zXIiI/soy6ckRi0meffcYxxxzDM888w09+8hOny4kZt912G7fffjs7d+7kgAMOcLockQ5JPQciMaC2CzHNnj0bl8vFKaec4kBFIhLPNKwgEgPuvfdePv74Y04//XQ8Hg+LFi1i0aJF/PznP+eggw5yujwRiTMKByIxYPjw4SxdupQ777yT0tJS+vTpw2233cb//d//OV2aiMQh9RyIiIhIDeo5EBERkRoUDkRERKQGhQMRERGpQeFAREREalA4EBERkRoUDkRERKQGhQMRERGpQeFAREREalA4EBERkRoUDkRE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w/z589FosjRE4SDOFBYWMn36dIwxeoMQiROZmZksWbKEc845h+uvv55rrrmGkpISp8uSGKZwEEe++uorzjzzTFatWsX333+vLmaROJKcnMwDDzzAnDlzWLp0KWPHjuWTTz5xuiyJUQoHceKVV17h7LPPJjk5mcWLF3PwwQc7XZKIOOD8889n6dKlpKenM3HiRObMmYNt206XJTFGUxk7uEgkwj333MPcuXOZOHEif/7zn0lMTHS6LBFxWCgU4k9/+hN//etfOfnkk3nooYfo3r2702VJjFA46MAKCwv5xS9+wX//+19++9vf8vOf/1xDCSJSw/Lly7n22msJhULMnj2b0aNHO12SxACFgw7qq6++YsqUKZSUlPDII49w8sknO12SiMSo/Px8brjhBpYuXcr06dOZNWsWPp/P6bLEQeo56ICq+gs6derE4sWLFQxEpF5paWk8+eST3HXXXfzzn//krLPOYt26dU6XJQ5SOOhAwuEwd911FzNmzGD8+PG88sorHHTQQU6XJSLtgGVZTJ06lUWLFhEMBhk3bhzPPfecpjzHKQ0rdBAFBQXMmDGDFStWqL9ARPZLeXk5t956K88++yznnHMO9913n67PEGcUDjqANWvWMHXqVPUXiEiLeu2117j55ptJTU1l7ty5HHfccU6XJG1Ewwrt3Msvv6z+AhFpFWeffTZvvfUWPXv25Pzzz+eBBx7Q8utxQmcO2qlwOMw999zDww8/zKRJk7jvvvu0foGItIpwOMzs2bOZPXs2J554In/961/p2bOn02VJK1I4aIeq+gtycnL47W9/yxVXXKH+AhFpde+//z5XX301FRUV3H///YwfP77Z+4oYQ14gwo6KMDsrIpSFbSLG4LYskj0uuiW6yUj0kO5349b7W5tTOGhn1qxZw5QpUygrK+Nvf/ubhhFEpE0VFhZy0003sWjRIi6//HJ+97vf4ff7G/344mCE1fmV5OYHKAsZbGNwWRb2Hoeiqq9dlkWy1yIzzc+QtARSfe7WeElSC4WDduSll17ixhtvZODAgTz++OP07t3b6ZJEJA4ZY3jmmWf43e9+R//+/Zk7dy6DBw+u9zGVEZsVW8vJza8kYgwY8LgsXFDrmU9jDDYQtg1Y4LYsMtMSGNEziQS32uVam77D7UA4HOaOO+7gqquuYsKECbzyyisKBiLiGMuyuOyyy1i8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P/44d955JwMGDGDOnDkcdthhTpclIh2EMYa5c+dyzz33cNpppzFnzhw6d+7sdFnSTGpIjBOWZTFt2jQWLVoEwJlnnsnf//53bNt2uDIRae/KysqYMWMGd999N9deey3//Oc/FQzaOZ05iEOVlZXcfffd/P3vf+e0005j9uzZZGRkOF2WiLRDGzZsYOrUqWzatIkHH3yQs846y+mSpAUoHMSx//znP/zyl78kEonwwAMPMGbMGKdLEpF25O233+aqq64iPT2dJ554QuuqdCAaVohjp59+Om+//TZDhw7lZz/7Gb/+9a+pqKhwuiwRiXHGGB566CEuu+wyTjzxRBYtWqRg0MHozIFgjOHpp5/mtttuo3fv3sydO7fOC6GISHwrLS3ll7/8JW+88QY33nhj9VRp6Vj0ExUsy+KnP/0pb775JgkJCUyYMIGHH35YzYoiUsP69es5++yzee+993jiiSe4+eabFQw6KP1UpdohhxzC66+/zvTp07nzzju56KKL2LZtm9NliUgMeOuttzjzzDOJRCK88cYbjBs3zumSpBUpHEgNPp+P3/72tzz//PN8++23nHHGGdXTH0UkPhlj6NGjByNHjuSNN95g4MCBTpckrUw9B1KngoICbr75ZhYtWsQll1zC7bffTnJystNliYhDqpZ0l45P4UDqZYzh3//+N7NmzaJHjx7MmTOHo48+2umyRESkFWlYQeplWRYXX3wxS5cuJTU1lXPPPZcPP/xQl4EWEenAdOZAGi0UCrF06VLOPPNMoPbLTIuISPuncCBNpnFHEZGOTcMK0mQKBiLtSHExzJwJ55wDI0fCrFlQUADjxkFtsw4eewyys9u+TokpCgciIh3ZjBkwfjy8+iq8+y588w188QU8/zwce2zNbUMhWL3amTolpigciIh0VMuXwyefwF13wejRMHYsbNwILhd06bLv9gsXwsSJbV2lxCCP0wVIB1FcDL//ffRTR2EhnHoq3HQTXHQRfPcdrFsX3S4UgmuugV27YNQouOoqR8sW6dC+/BIuvTQ6lNAQ24Z33oG//Q3mzm310iS26cyBtIzGnrpctAiOOy76CSU3F/LynKtZpKM78MDoAb+sLPp1IBD9t1mbN96InlkQQeFAWkJTTl1u3AiHHRb9+yGHwGeftXW1IvFjwgQ48cTov8vRo2HSJNi0qfZtv/suGuYvuSR6BvBf/2rbWiWmaFhB9l9TTl0efDCsXAkjRsCqVdGvRaR1uN1w992135edHf23m50Nd9wB118f/VN13yWXtF2dEnN05kD2X1NOXY4dGx1KuPBCSE+HAw5oszJFZA/z5sGaNdH/Dh68730S1xQOZP815dSl2w1/+EP09KXXC0OHtm2tIiLSIK2QKK2r6tTlEUdET1126gTXXRftR7jiCjVAiYjEIIUDERERqUHDCiIiHYQ+60lLUTgQEekANmzYgDGm+o/I/lA4EBFp51544QVOP/10rrrqKioqKnRxNNlvCgcSc/TJR6RxwuEwt956K9dccw3nnnsus2fPJikpyemypANQOJCYURUInn/+eaZNm0Z+fr7DFYnErry8PC666CKeeOIJ7r77bmbPno3f73e6LOkgFA4kZlSdCu3atSurVq1i1KhRvPfeew5XJRJ7cnNzGTduHGvXrmXevHlMmTJFQwnSohQOJOaMGzeOt99+m0GDBnHRRRdx++23EwwGnS5LJCbMnz+fc889l4yMDN58801OOukkp0uSDkjrHEjMsm2bv//979x9990MHDiQuXPnMmjQIKfLEnFEKBTi9ttv5/HHH+fCCy/kD3/4AwkJCU6XJR2UzhxIzHK5XPz85z9n0aJFRCIRxo8fzxNPPKFmRYk7O3fu5MILL+Spp57innvu4f7771cwkFalMwfSLgQCAe68806eeOIJRo8ezf33388BumiTxIFPP/2UadOmEYlEeOyxxzjhhBOcLknigM4cSLvg9/u5++67efrpp/nss88444wzePvtt50uS6RV/fvf/2bixIn06tWLN998U8FA2ozCgbQro0aNYtmyZRx11FFceumlzJo1i0Ag4HRZIi0qFArx61//mhtvvJHs7GwWLlxIjx49nC5L4oiGFaRdMsbw5JNPcvvtt9O/f3/mzp3LYYcd5nRZIvttx44dXHHFFXz22WfcfffdXHrppU6XJHFIZw6kXbIsiylTpvDmm2/icrkYP348jz32GLZtO12aSLN9/PHHjBs3jo0bN/LCCy8oGIhjFA6kXRs0aBBvvPEGU6ZM4dZbb+UnP/kJ27dvd7oskSZ79tlnmTRpEgcddBCLFy9m6NChTpckcUzDCtJhvPvuu1x//fWEQiEeeOABxo4d63RJIg0KBoPMmjWLZ555hp/97GfccccdeL1ep8uSOKdw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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " - Graph with 10 vertices and 20 edges.\n", + " - Features dimensions: [1, 0]\n", + " - There are 0 isolated nodes.\n", + "\n" + ] + } + ], + "source": [ + "lifted_dataset = PreProcessor(dataset, transform_config, loader.data_dir)\n", + "describe_data(lifted_dataset)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create and Run a Graph NN Model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this section a simple model is created to test that the used lifting works as intended. In this case the model uses the `up_laplacian_1` and the `down_laplacian_1` so the lifting should make sure to add them to the data." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from torch_geometric.nn.models import GraphSAGE" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "model = GraphSAGE(\n", + " in_channels=-1,\n", + " hidden_channels=32,\n", + " out_channels=2,\n", + " num_layers=2,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "y_hat = model(x=lifted_dataset.x, edge_index=lifted_dataset.edge_index)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}