📚 The CoCalc Library - books, templates and other resources
License: OTHER
Small World Graphs
Code examples from Think Complexity, 2nd edition.
Copyright 2016 Allen Downey, MIT License
Regular ring lattice
To make a ring lattice, I'll start with a generator function that yields edges between each node and the next halfk neighbors.
We can test it with 3 nodes and halfk=1
Now we use adjacent_edges to write make_ring_lattice
And we can test it out with n=10 and k=4
Exercise: To see how this function fails when k is odd, run it again with k=3 or k=5.
WS graph
To make a WS graph, you start with a ring lattice and then rewire.
Here's the function that does the rewiring
Here's an example with p=0.2
Just checking that we have the same number of edges we started with:
Now I'll generate a plot that shows WS graphs for a few values of p
Exercise: What is the order of growth of rewire?
Clustering
The following function computes the local clustering coefficient for a given node, u:
The network average clustering coefficient is just the mean of the local CCs.
In a ring lattice with k=4, the clustering coefficient for each node should be 0.5
And the network average should be 0.5
Correct.
Exercise: Write a version of node_clustering that replaces the for loop with a list comprehension. Is it faster?
Exercise: What is the order of growth of clustering_coefficient in terms of n, m, and k?
Path length
The following function computes path lengths between all pairs of nodes
The characteristic path length is the mean path length for all pairs.
On a complete graph, the average path length should be 1
On a ring lattice with n=1000 and k=10, the mean is about 50
Exercise: What is the mean path length in a ring lattice with n=10 and k=4?
The experiment
This function generates a WS graph with the given parameters and returns a pair of (mean path length, clustering coefficient):
With n=1000 and k=10, it takes a few seconds on my computer:
Now we'll run it with a range of values for p.
This function runs each value of p 20 times and returns a dictionary that maps from each p to a list of (mpl, cc) pairs.
Here are the raw results. Warning: this takes a few minutes to run.
Let's get the results into a form that's easy to plot.
And normalize them so they both start at 1.0
Here's the plot that replicates Watts and Strogatz's Figure 2.
Breadth-first search
Now let's see how the shortest path algorithm works. We'll start with BFS, which is the basis for Dijkstra's algorithm.
Here's our old friend, the ring lattice:
And here's my implementation of BFS using a deque.
It works:
Here's a version that's a little faster, but maybe less readable.
It works, too.
Dijkstra's algorithm
Now we're ready for Dijkstra's algorithm, at least for graphs where all the edges have the same weight/length.
Again, we'll test it on a ring lattice.
Here's my implementation:
And here's the result from NetworkX:
They are the same:
Exercise: In a ring lattice with n=1000 and k=10, which node is farthest from 0 and how far is it? Use shortest_path_dijkstra to check your answer.
Note: the maximum distance between two nodes is the diameter of the graph.
Exercises
Exercise: In a ring lattice, every node has the same number of neighbors. The number of neighbors is called the degree of the node, and a graph where all nodes have the same degree is called a regular graph.
All ring lattices are regular, but not all regular graphs are ring lattices. In particular, if k is odd, we can't construct a ring lattice, but we might be able to construct a regular graph.
Write a function called make_regular_graph that takes n and k and returns a regular graph that contains n nodes, where every node has k neighbors. If it's not possible to make a regular graph with the given values of n and k, the function should raise a ValueError.
Exercise: My implementation of reachable_nodes_bfs is efficient in the sense that it is in , but it incurs a lot of overhead adding nodes to the queue and removing them. NetworkX provides a simple, fast implementation of BFS, available from the NetworkX repository on GitHub.
Here is a version I modified to return a set of nodes:
Compare this function to reachable_nodes_bfs and see which is faster. Then see if you can modify this function to implement a faster version of shortest_path_dijkstra
Exercise: The following implementation of a BFS contains two performance errors. What are they? What is the actual order of growth for this algorithm?
Exercise: In the book, I claimed that Dijkstra's algorithm does not work unless it uses BFS. Write a version of shortest_path_dijkstra that uses DFS and test it on a few examples to see what goes wrong.