|
| 1 | +import random |
| 2 | +from itertools import combinations |
| 3 | + |
| 4 | +import networkx as nx |
| 5 | +from toponetx.classes import SimplicialComplex |
| 6 | +from torch_geometric.data import Data |
| 7 | + |
| 8 | +from modules.transforms.liftings.graph2simplicial.base import ( |
| 9 | + Graph2SimplicialLifting, |
| 10 | +) |
| 11 | + |
| 12 | + |
| 13 | +class SimplicialDnDLifting(Graph2SimplicialLifting): |
| 14 | + r"""Lifts graphs to simplicial complex domain using a Dungeons & Dragons inspired system. |
| 15 | +
|
| 16 | + Parameters |
| 17 | + ---------- |
| 18 | + **kwargs : optional |
| 19 | + Additional arguments for the class. |
| 20 | + """ |
| 21 | + |
| 22 | + def __init__(self, **kwargs): |
| 23 | + super().__init__(**kwargs) |
| 24 | + |
| 25 | + def lift_topology(self, data: Data) -> dict: |
| 26 | + r"""Lifts the topology of a graph to a simplicial complex using Dungeons & Dragons (D&D) inspired mechanics. |
| 27 | +
|
| 28 | + Parameters |
| 29 | + ---------- |
| 30 | + data : Data |
| 31 | + The input data to be lifted. |
| 32 | +
|
| 33 | + Returns |
| 34 | + ------- |
| 35 | + dict |
| 36 | + The lifted topology. |
| 37 | + """ |
| 38 | + graph = self._generate_graph_from_data(data) |
| 39 | + simplicial_complex = SimplicialComplex() |
| 40 | + |
| 41 | + characters = self._assign_attributes(graph) |
| 42 | + simplices = [set() for _ in range(2, self.complex_dim + 1)] |
| 43 | + |
| 44 | + for node in graph.nodes: |
| 45 | + simplicial_complex.add_node(node, features=data.x[node]) |
| 46 | + |
| 47 | + for node in graph.nodes: |
| 48 | + character = characters[node] |
| 49 | + for k in range(1, self.complex_dim): |
| 50 | + dice_roll = self._roll_dice(character, k) |
| 51 | + neighborhood = list( |
| 52 | + nx.single_source_shortest_path_length( |
| 53 | + graph, node, cutoff=dice_roll |
| 54 | + ).keys() |
| 55 | + ) |
| 56 | + for combination in combinations(neighborhood, k + 1): |
| 57 | + simplices[k - 1].add(tuple(sorted(combination))) |
| 58 | + |
| 59 | + for set_k_simplices in simplices: |
| 60 | + simplicial_complex.add_simplices_from(list(set_k_simplices)) |
| 61 | + |
| 62 | + return self._get_lifted_topology(simplicial_complex, graph) |
| 63 | + |
| 64 | + def _assign_attributes(self, graph): |
| 65 | + """Assign D&D-inspired attributes based on node properties.""" |
| 66 | + degrees = nx.degree_centrality(graph) |
| 67 | + clustering = nx.clustering(graph) |
| 68 | + closeness = nx.closeness_centrality(graph) |
| 69 | + eigenvector = nx.eigenvector_centrality(graph) |
| 70 | + betweenness = nx.betweenness_centrality(graph) |
| 71 | + pagerank = nx.pagerank(graph) |
| 72 | + |
| 73 | + attributes = {} |
| 74 | + for node in graph.nodes: |
| 75 | + attributes[node] = { |
| 76 | + "Degree": degrees[node], |
| 77 | + "Clustering": clustering[node], |
| 78 | + "Closeness": closeness[node], |
| 79 | + "Eigenvector": eigenvector[node], |
| 80 | + "Betweenness": betweenness[node], |
| 81 | + "Pagerank": pagerank[node], |
| 82 | + } |
| 83 | + return attributes |
| 84 | + |
| 85 | + def _roll_dice(self, attributes, k): |
| 86 | + """Simulate a D20 dice roll influenced by node attributes where a different attribute is used based on the simplex level.""" |
| 87 | + |
| 88 | + attribute = None |
| 89 | + if k == 1: |
| 90 | + attribute = attributes["Degree"] |
| 91 | + elif k == 2: |
| 92 | + attribute = attributes["Clustering"] |
| 93 | + elif k == 3: |
| 94 | + attribute = attributes["Closeness"] |
| 95 | + elif k == 4: |
| 96 | + attribute = attributes["Eigenvector"] |
| 97 | + elif k == 5: |
| 98 | + attribute = attributes["Betweenness"] |
| 99 | + else: |
| 100 | + attribute = attributes["Pagerank"] |
| 101 | + |
| 102 | + base_roll = random.randint(1, 20) |
| 103 | + modifier = int(attribute * 20) |
| 104 | + return base_roll + modifier |
0 commit comments