Preferential Attachment Without Calculating The Degree in Python
Preferential attachment is a fundamental concept in network theory that describes how new nodes tend to connect to existing nodes with higher degrees. This article explores how to implement preferential attachment in Python without explicitly calculating node degrees.
What is Preferential Attachment?
Preferential attachment is a growth model for networks where the probability of a new node connecting to an existing node is proportional to the number of connections that node already has. This leads to the emergence of scale-free networks, where a few nodes have many connections while most have few.
The probability that a new node connects to node i is given by:
P(i) = ki / Σkj
where ki is the degree of node i, and the sum is over all existing nodes.
The model was first proposed by Barabási and Albert in 1999 to explain the scale-free nature of many real-world networks, including the World Wide Web and social networks.
Implementation Without Degree Calculation
While the mathematical definition involves calculating node degrees, we can implement preferential attachment efficiently without explicitly tracking degrees by using a technique called "reservoir sampling" or by maintaining a running sum of degrees.
Approach Using Running Sum
Here's a Python implementation that maintains a running sum of degrees to efficiently select nodes according to preferential attachment:
This implementation avoids explicitly calculating degrees by maintaining a running sum of degrees, which allows us to select nodes proportionally to their degrees without recalculating the entire degree distribution each time.
Python Implementation
import random
class PreferentialAttachmentNetwork:
def __init__(self, initial_nodes=2):
self.nodes = list(range(initial_nodes))
self.edges = []
self.degree_sum = initial_nodes * (initial_nodes - 1) // 2
# Initialize with a complete graph
for i in range(initial_nodes):
for j in range(i + 1, initial_nodes):
self.edges.append((i, j))
def add_node(self):
new_node = len(self.nodes)
self.nodes.append(new_node)
# Select a node to connect to based on preferential attachment
target = self._select_node()
self.edges.append((new_node, target))
self.degree_sum += 2 # New node has degree 1, target gains 1
def _select_node(self):
r = random.uniform(0, self.degree_sum)
cumulative = 0
for node in self.nodes:
# Calculate degree without storing it
degree = sum(1 for edge in self.edges if node in edge)
cumulative += degree
if cumulative > r:
return node
return self.nodes[-1] # Fallback
# Example usage
network = PreferentialAttachmentNetwork()
for _ in range(10):
network.add_node()
print(f"Nodes: {network.nodes}")
print(f"Edges: {network.edges}")
print(f"Degree sum: {network.degree_sum}")
This implementation efficiently selects nodes according to preferential attachment without explicitly calculating and storing node degrees for each selection.
Practical Example
Let's walk through a simple example to demonstrate how the algorithm works:
- Start with a network of 2 nodes connected by an edge.
- Add a new node that connects to either existing node with equal probability (50% each).
- Add another new node that connects to the node with higher degree (the one that was connected first).
- Continue this process, observing how the network evolves toward a scale-free structure.
| Step | Nodes | Edges | Degree Sum |
|---|---|---|---|
| Initial | 0, 1 | (0,1) | 2 |
| Add Node 2 | 0, 1, 2 | (0,1), (0,2) | 4 |
| Add Node 3 | 0, 1, 2, 3 | (0,1), (0,2), (0,3) | 6 |
| Add Node 4 | 0, 1, 2, 3, 4 | (0,1), (0,2), (0,3), (0,4) | 8 |
As you can see, node 0 becomes increasingly likely to receive new connections as the network grows, demonstrating the preferential attachment mechanism.