perturbations.py¶
Visualized below, there are three main types of perturbations - empty, complete, and random - that can be applied to either the node features or graph structure.
This file defines the Mode Perturbations, i.e Graph Transformations, of the RINGS Framework.
- class rings.perturbations.Original[source]¶
A placeholder transform that returns the input node features & graph data without modifications.
This transform serves as a baseline for comparing other transforms’ effects on graphs.
- Parameters:
None
Examples
>>> from torch_geometric.datasets import TUDataset >>> from rings.perturbations import Original >>> dataset = TUDataset(root='/tmp/MUTAG', name='MUTAG') >>> data = dataset[0] # Get the first graph >>> transform = Original() >>> transformed_data = transform(data) >>> # The transformed data is identical to the original data >>> assert transformed_data == data
- class rings.perturbations.EmptyFeatures[source]¶
A transform that assigns identical features (zero vector) to each node.
This transform removes all node feature information by replacing existing features with identical zero vectors, preserving only graph structure information.
- Parameters:
None
Examples
>>> from torch_geometric.datasets import TUDataset >>> from rings.perturbations import EmptyFeatures >>> dataset = TUDataset(root='/tmp/PROTEINS', name='PROTEINS') >>> data = dataset[0] # Get the first graph >>> transform = EmptyFeatures() >>> transformed_data = transform(data) >>> # Check that all node features are zero vectors >>> import torch >>> assert torch.all(transformed_data.x == 0) >>> # Check that all node features have dimension 1 >>> assert transformed_data.x.size(1) == 1
- class rings.perturbations.CompleteFeatures(max_nodes)[source]¶
A transform that assigns unique node IDs as features to each node.
Each node is represented by a padded one-hot encoded vector, creating maximally distinctive node features where each node can be uniquely identified.
- Parameters:
max_nodes (int) – Maximum number of nodes for one-hot feature encoding. This determines the dimension of the one-hot vectors.
Examples
>>> import torch >>> from torch_geometric.data import Data >>> from rings.perturbations import CompleteFeatures >>> # Create a graph with 3 nodes >>> edge_index = torch.tensor([[0, 1, 1, 2], [1, 0, 2, 1]], dtype=torch.long) >>> data = Data(edge_index=edge_index, num_nodes=3) >>> # Transform with max_nodes=5 >>> transform = CompleteFeatures(max_nodes=5) >>> transformed_data = transform(data) >>> print(transformed_data.x) tensor([[1., 0., 0., 0., 0.], [0., 1., 0., 0., 0.], [0., 0., 1., 0., 0.]])
- class rings.perturbations.RandomFeatures(shuffle=False, fixed_dimension=None)[source]¶
A transform that randomizes node features.
This transform either samples new features from a standard normal distribution or shuffles existing node features between nodes, effectively destroying any meaningful correlation between node features and graph structure while preserving the feature distribution.
- Parameters:
shuffle (bool, default=False) – If True, shuffle existing node features among nodes. If False, replace features with random values from a standard normal distribution.
fixed_dimension (int, optional) – Fixed dimension for new random features. If None, use the original feature dimension. Only used when shuffle=False.
Examples
>>> import torch >>> from torch_geometric.data import Data >>> from rings.perturbations import RandomFeatures >>> # Create a simple graph with features >>> x = torch.tensor([[1, 2], [3, 4], [5, 6]], dtype=torch.float) >>> edge_index = torch.tensor([[0, 1], [1, 2]], dtype=torch.long) >>> data = Data(x=x, edge_index=edge_index) >>> >>> # Example 1: Random normal features >>> transform1 = RandomFeatures(shuffle=False) >>> t1_data = transform1(data.clone()) >>> # Features will be different, but dimensions preserved >>> assert t1_data.x.shape == data.x.shape >>> assert not torch.allclose(t1_data.x, data.x) >>> >>> # Example 2: Shuffled features >>> transform2 = RandomFeatures(shuffle=True) >>> t2_data = transform2(data.clone()) >>> # Original features should be present but in different order >>> original_set = {tuple(row.tolist()) for row in data.x} >>> shuffled_set = {tuple(row.tolist()) for row in t2_data.x} >>> assert original_set == shuffled_set >>> >>> # Example 3: Random features with fixed dimension >>> transform3 = RandomFeatures(fixed_dimension=5) >>> t3_data = transform3(data.clone()) >>> assert t3_data.x.shape == (3, 5) # 3 nodes, 5 features
- __init__(shuffle=False, fixed_dimension=None)[source]¶
Initialize the RandomFeatures transform.
- Parameters:
shuffle (bool, default=False) – If True, shuffle existing node features among nodes. If False, replace features with random values from a standard normal distribution.
fixed_dimension (int, optional) – Fixed dimension for new random features. If None, use the original feature dimension. Only used when shuffle=False.
- class rings.perturbations.EmptyGraph[source]¶
A transform that removes all edges from the graph, creating an empty graph.
This transform preserves node features but removes all edges, thereby eliminating any graph structure information. The resulting graph consists of isolated nodes.
- Parameters:
None
Examples
>>> import torch >>> from torch_geometric.data import Data >>> from rings.perturbations import EmptyGraph >>> # Create a simple graph with features and edges >>> x = torch.tensor([[1, 2], [3, 4], [5, 6]], dtype=torch.float) >>> edge_index = torch.tensor([[0, 1, 1], [1, 0, 2]], dtype=torch.long) >>> data = Data(x=x, edge_index=edge_index) >>> >>> # Apply transform >>> transform = EmptyGraph() >>> transformed_data = transform(data) >>> >>> # Check that all edges are removed >>> assert transformed_data.edge_index.shape == (2, 0) >>> # Check that node features remain unchanged >>> assert torch.equal(transformed_data.x, data.x)
- class rings.perturbations.CompleteGraph[source]¶
A transform that replaces the existing graph structure with a complete graph.
This transform preserves node features but connects every pair of nodes with an edge, creating a fully connected graph where each node is directly connected to all other nodes. Self-loops are excluded.
- Parameters:
None
Examples
>>> import torch >>> from torch_geometric.data import Data >>> from rings.perturbations import CompleteGraph >>> # Create a simple graph with 3 nodes >>> edge_index = torch.tensor([[0, 1], [1, 2]], dtype=torch.long) # 0-1-2 path >>> data = Data(edge_index=edge_index, num_nodes=3) >>> >>> # Apply transform >>> transform = CompleteGraph() >>> transformed_data = transform(data) >>> >>> # In a complete graph with 3 nodes, there should be 6 directed edges (3×2) >>> assert transformed_data.edge_index.shape[1] == 6 >>> >>> # Check that every possible edge (except self-loops) exists >>> edges = set(zip(transformed_data.edge_index[0].tolist(), ... transformed_data.edge_index[1].tolist())) >>> expected_edges = {(0,1), (1,0), (0,2), (2,0), (1,2), (2,1)} >>> assert edges == expected_edges
- class rings.perturbations.RandomGraph(p=None, shuffle=False)[source]¶
A transform that replaces the existing graph structure with a random graph.
The graph is generated either using an Erdos-Renyi model (with probability p) or by randomly shuffling the current edges. Node features are preserved while graph structure is randomized.
- Parameters:
p (float, optional) – Probability of an edge existing between any two nodes in the Erdos-Renyi model. If None, the same number of edges as in the original graph is used.
shuffle (bool, default=False) – If True, shuffle the existing graph structure instead of creating a new one.
Examples
>>> import torch >>> from torch_geometric.data import Data >>> from rings.perturbations import RandomGraph >>> # Create a simple graph >>> edge_index = torch.tensor([[0, 1, 1, 2], [1, 0, 2, 1]], dtype=torch.long) >>> data = Data(edge_index=edge_index, num_nodes=4) >>> >>> # Example 1: Random graph with same number of edges >>> torch.manual_seed(42) # For reproducibility >>> transform1 = RandomGraph() >>> t1_data = transform1(data.clone()) >>> # Should have same number of edges but different structure >>> assert t1_data.edge_index.shape[1] == data.edge_index.shape[1] >>> assert not torch.equal(t1_data.edge_index, data.edge_index) >>> >>> # Example 2: Random graph with specified edge probability >>> transform2 = RandomGraph(p=0.5) >>> t2_data = transform2(data.clone()) >>> # Expected edges with p=0.5: 0.5 * 4 * 3 / 2 = 3 (before removing self-loops) >>> >>> # Example 3: Shuffle existing edges >>> transform3 = RandomGraph(shuffle=True) >>> t3_data = transform3(data.clone()) >>> # Should have same number of edges >>> assert t3_data.edge_index.shape[1] == data.edge_index.shape[1]
- __init__(p=None, shuffle=False)[source]¶
Initialize the RandomGraph transform.
- Parameters:
p (float, optional) – Probability of an edge existing between any two nodes in the Erdos-Renyi model. If None, the same number of edges as in the original graph is used.
shuffle (bool, default=False) – If True, shuffle the existing graph structure instead of creating a new one.
- class rings.perturbations.RandomConnectedGraph(p=None, shuffle=False)[source]¶
A transform that replaces the existing graph structure with a random connected graph.
The graph is generated either by randomly shuffling the current edges or using a modified Erdos-Renyi model ensuring connectivity. The resulting graph is guaranteed to be connected, meaning there exists a path between any two nodes.
- Parameters:
p (float, optional) – Edge probability parameter. If None, the same number of edges as in the original graph is used. The actual probability is adjusted to ensure connectivity.
shuffle (bool, default=False) – If True, shuffle edges of the existing graph instead of creating a new structure. Multiple shuffle attempts may be performed until a connected graph is achieved.
Examples
>>> import torch >>> import networkx as nx >>> from torch_geometric.data import Data >>> from torch_geometric.utils import to_networkx >>> from rings.perturbations import RandomConnectedGraph >>> >>> # Create a simple graph >>> edge_index = torch.tensor([[0, 1, 2], [1, 2, 3]], dtype=torch.long) >>> data = Data(edge_index=edge_index, num_nodes=4) >>> >>> # Apply transform >>> torch.manual_seed(42) # For reproducibility >>> transform = RandomConnectedGraph(p=0.5) >>> transformed_data = transform(data.clone()) >>> >>> # Convert to networkx to check connectivity >>> G = to_networkx(transformed_data, to_undirected=True) >>> assert nx.is_connected(G) >>> >>> # With shuffle=True >>> transform2 = RandomConnectedGraph(shuffle=True) >>> t2_data = transform2(data.clone()) >>> G2 = to_networkx(t2_data, to_undirected=True) >>> assert nx.is_connected(G2)
- __init__(p=None, shuffle=False)[source]¶
Initialize the RandomConnectedGraph transform.
- Parameters:
p (float, optional) – Edge probability parameter. If None, the same number of edges as in the original graph is used.
shuffle (bool, default=False) – If True, shuffle edges instead of creating a new structure.