Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate Newman-Watts Graphs #106

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b9da1cb
Add generator for cycle graph
Apr 19, 2018
2a3da41
Specify out-degree when generating a circle.
Apr 23, 2018
a88a01c
Precodition on number of nodes in graph
Apr 23, 2018
f8ec24b
Compute additional edges after Newman and Watts
Apr 23, 2018
39ee638
Generator for Newman-Watts Small World Graph
Apr 24, 2018
72a6d29
Fix bug in gen-newman-watts
Apr 24, 2018
2b7aba1
Add tests for generating circles fŕom the different graph types
Apr 24, 2018
c1969f0
Tests for nodes and edges in circle-graph
Apr 24, 2018
3c7a02e
Add function and test for clustering coefficient
Apr 25, 2018
a41c002
Add function and test for global clustering coefficient
Apr 25, 2018
f9a9a08
Add tests for Newman-Watts Graph
Apr 25, 2018
e5a0a1c
Add test for special case in newman-watts-graph
Apr 30, 2018
b8003bd
ignore .idea/*
Jun 12, 2018
b03e89f
Implement initial-barabasi-albert
Jul 9, 2018
e7965a1
Add num-initial as argument to inital-barabasi-albert
Jul 12, 2018
55a148f
Pass java.util.Random to initial-barabasi-albert instead of seed
Jul 12, 2018
dd12416
Fix value for new-node in initial-barabasi-albert
Jul 12, 2018
d2713a9
Change order of arguments for gen-barabasi-albert
Jul 12, 2018
9e0606b
Fix test for gen-barabasi-albert
Jul 12, 2018
c9dfd79
Implement function to generate Barabasi-Albert-Graph
Jul 18, 2018
b2e18d3
Make gen-barabasi-albert a variadic function
Jul 19, 2018
93085c7
Add tests for gen-barabasi-albert
Jul 19, 2018
25abdf7
Clean up a bit
Sep 6, 2018
f8d617d
Fix tests for Barabasi-Albert Graphs
Sep 6, 2018
1d4b1c7
Fix test for number of edges in Barabasi-Albert Graphs
Sep 6, 2018
fb92666
Remove gen-barabasi-albert and gen-barabasi-albert-test
Sep 12, 2018
80bcd6d
Format docstrings
Sep 12, 2018
868c5ad
Remove .iml-File form index
Sep 14, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ classes
/.lein-*
.nrepl-port
out

*iml
.idea/*
37 changes: 34 additions & 3 deletions src/loom/alg.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ can use these functions."
out-degree in-degree weighted? directed? graph digraph transpose]
:as graph]
[loom.alg-generic :refer [trace-path preds->span]]
#?(:clj [clojure.data.priority-map :as pm]
:cljs [tailrecursion.priority-map :as pm])
[clojure.set :as clj.set]))
#?(:clj
[clojure.data.priority-map :as pm]
:cljs [tailrecursion.priority-map :as pm])
[clojure.set :as clj.set]
[clojure.set :as set]))

;;;
;;; Convenience wrappers for loom.alg-generic functions
Expand Down Expand Up @@ -792,3 +794,32 @@ can use these functions."
(edges g1))))))

;; ;; Todo: MST, coloring, matching, etc etc

(defn clustering-coefficient
"Computes clustering coefficient as described in Watts and Strogatz (1998).
When g and node are supplied as arguments, returns the local clustering
coefficient for node in g. When only g is supplied as an argument, returns
the average over all clustering coefficients. For details see
Watts, Duncan J., and Steven H. Strogatz. “Collective Dynamics of ‘Small-World’
Networks.” Nature 393, no. 6684 (June 1998): 440–42. https://doi.org/10.1038/30918."
([g node]
(let [neighbours (successors g node)
potential-connections (/ (* (count neighbours) (dec (count neighbours))) 2)]
(if (> (count neighbours) 1)
;; how many connections between the neighbours
;; of node are there?
(let [neighbour-overlaps (for [n neighbours]
(let [potential (clj.set/difference neighbours #{n})
actual (successors g n)
overlap (clj.set/intersection potential actual)]
(count overlap)))
sum-overlaps (reduce + 0 neighbour-overlaps)]
(/ sum-overlaps potential-connections 2))
;; if there are not at least 2 neighbours,
;; clustering-coefficient is set to zero
0)))
([g]
(let [nodeset (nodes g)
sum-coeffs (reduce #(+ %1 (clustering-coefficient g %2)) 0 nodeset)]
(/ sum-coeffs (count nodeset)))))

37 changes: 36 additions & 1 deletion src/loom/gen.clj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(ns ^{:doc "Graph-generating functions"
:author "Justin Kramer"}
loom.gen
(:require [loom.graph :refer [weighted? directed? add-nodes* add-edges*]]))
(:require [loom.graph :refer [weighted? directed? add-nodes* add-edges* nodes]]))

(defn gen-rand
"Adds num-nodes nodes and approximately num-edges edges to graph g. Nodes
Expand Down Expand Up @@ -53,3 +53,38 @@
(-> g
(add-nodes* nodes)
(add-edges* edges))))

(defn gen-circle
"Adds num-nodes nodes to graph g and connects each one to out-degree
other nodes."
[g num-nodes out-degree]
{:pre [(> num-nodes (* out-degree 2))]}
(let [nodes (range num-nodes)
edges (for [n nodes
d (range 1 (inc out-degree))]
[n (mod (+ n d) (count nodes))])]
(-> g
(add-nodes* nodes)
(add-edges* edges))))

(defn ^:private add-shortcuts
"Computes additional edges for graph g as described in Newman and
Watts (1999)."
([g phi seed]
(let [rnd (java.util.Random. seed)
nodes (loom.graph/nodes g)
shortcuts (for [n nodes
:when (> phi (.nextDouble rnd))]
[n (.nextInt rnd (count nodes))])]
(-> g
(add-edges* shortcuts)))))

(defn gen-newman-watts
"Generate a graph with small-world properties as described in Newman
and Watts (1999)."
([g num-nodes out-degree phi seed]
(-> g
(gen-circle num-nodes out-degree)
(add-shortcuts phi seed)))
([g num-nodes out-degree phi]
(gen-newman-watts g num-nodes out-degree phi (System/nanoTime))))
24 changes: 23 additions & 1 deletion test/loom/test/alg.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
coloring? greedy-coloring prim-mst-edges
prim-mst-edges prim-mst astar-path astar-dist
degeneracy-ordering maximal-cliques
subgraph? eql? isomorphism?]]
subgraph? eql? isomorphism?
clustering-coefficient]]
[loom.derived :refer [mapped-by]]
clojure.walk
#?@(:clj [[clojure.test :refer :all]]
Expand Down Expand Up @@ -627,3 +628,24 @@
false (isomorphism? g7 (mapped-by inc g7) dec)
false (isomorphism? (digraph) (graph) identity)
false(isomorphism? (digraph [1 2]) (graph [1 2]) identity)))

(deftest clustering-coefficient-test
(let [g1 (graph {0 #{}, 1 #{}, 2 #{}, 3 #{}}) ; empty graph
g2 (graph {0 #{1 2 3}, 1 #{0 2 3}, 2 #{0 1 3}, 3 #{0 1 2}}) ; fully connected
g3 (graph {0 #{1 4}, 1 #{2 0}, 2 #{3 1}, 3 #{4 2}, 4 #{0 3}}) ; circle
g4 (graph {0 #{1 2 3}, 1 #{0 3}, 2 #{0}, 3 #{0 1}})
g5 (graph [0 1] [0 2] [0 3] [1 3])]
(testing "local clustering coefficients"
(are [expected got] (= expected got)
(clustering-coefficient g1 0) 0
(clustering-coefficient g2 1) 1
(clustering-coefficient g3 0) 0
(clustering-coefficient g4 0) (/ 1 3)
(clustering-coefficient g5 0) (/ 1 3)))
(testing "global clustering coefficients"
(are [expected got] (= expected got)
(clustering-coefficient g1) 0
(clustering-coefficient g2) 1
(clustering-coefficient g3) 0
(clustering-coefficient g4) (/ 7 12)
(clustering-coefficient g4) (clustering-coefficient g5)))))
69 changes: 69 additions & 0 deletions test/loom/test/gen.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
(ns loom.test.gen
(:require [clojure.test :refer (deftest testing is are)]
[loom.graph :refer (graph digraph weighted-graph weighted-digraph graph? nodes edges)]
[loom.alg :refer (clustering-coefficient)]
[loom.gen :refer (gen-circle gen-newman-watts gen-barabasi-albert)]))

(deftest build-circle-test
(let [g1 (gen-circle (graph) 5 1)
g2 (gen-circle (graph) 6 2)
g3 (gen-circle (digraph) 6 2)
g4 (gen-circle (weighted-graph {0 {1 42 2 42} 1 {2 42 3 42} 2 {3 42 4 42}}) 10 1)
g5 (gen-circle (weighted-graph) 10 1)
g6 (gen-circle (weighted-digraph [0 1 42] [1 2 42] [2 3 42] [1 0 43] [2 1 43] [3 2 43]) 10 1)
g7 (gen-circle (weighted-digraph) 10 1)]
(testing "Generating circle-graphs from the different graph types"
(are [graphs] (graph? graphs)
g1 g2 g3 g4 g5 g6 g7))
(testing "Nodes and Edges"
(are [expected got] (= expected got)
(into #{} (range 5)) (nodes g1)
(into #{} (range 6)) (nodes g2)
(into #{} (range 6)) (nodes g3)
(into #{} (range 10)) (nodes g4)
(into #{} (range 10)) (nodes g5)
(into #{} (range 10)) (nodes g6)
(into #{} (range 10)) (nodes g7)
#{[0 1] [1 0] [1 2] [2 1] [2 3] [3 2] [3 4] [4 3] [4 0] [0 4]} (set (edges g1))
#{[0 1] [1 0] [1 2] [2 1] [2 3] [3 2] [3 4] [4 3] [4 5] [5 4] [5 0] [0 5]
[0 2] [2 0] [1 3] [3 1] [2 4] [4 2] [3 5] [5 3] [4 0] [0 4] [1 5] [5 1]} (set (edges g2))
#{[0 1] [1 2] [2 3] [3 4] [4 5] [5 0] [0 2] [1 3] [2 4] [3 5] [4 0] [5 1]} (set (edges g3))
#{[0 1] [1 2] [2 3] [3 4] [4 5] [5 6] [6 7] [7 8] [8 9] [9 0]
[0 2] [1 3] [2 4]
[1 0] [2 1] [3 2] [4 3] [5 4] [6 5] [7 6] [8 7] [9 8] [0 9]
[2 0] [3 1] [4 2]} (set (edges g4))
#{[0 1] [1 2] [2 3] [3 4] [4 5] [5 6] [6 7] [7 8] [8 9] [9 0]
[1 0] [2 1] [3 2] [4 3] [5 4] [6 5] [7 6] [8 7] [9 8] [0 9]} (set (edges g5))
#{[0 1] [1 2] [2 3] [3 4] [4 5] [5 6] [6 7] [7 8] [8 9] [9 0]
[1 0] [2 1] [3 2]} (set (edges g6))
#{[0 1] [1 2] [2 3] [3 4] [4 5] [5 6] [6 7] [7 8] [8 9] [9 0]} (set (edges g7))))))

(deftest build-newman-watts-test
(let [g1 (gen-newman-watts (graph) 20 2 0.2)
g2 (gen-newman-watts (graph) 100 5 0.3)
g3 (gen-newman-watts (graph) 100 6 0)]
(testing "Construction, Nodes, Edges, Clustering coefficient for g1"
(is loom.graph/graph? g1)
(is (= 20 (count (nodes g1))))
(is (and
(>= (count (edges g1)) (* 2 20 2))
(<= (count (edges g1)) (+ (* 2 20 2) (* 20 2 0.35)))))
(is (and
(<= (- (/ 3 4) 0.5) (clustering-coefficient g1))
(>= (+ (/ 3 4) 0.5) (clustering-coefficient g1)))))
(testing "Construction, Nodes, Edges, Clustering coefficient for g2"
(is loom.graph/graph? g2)
(is (= 100 (count (nodes g2))))
(is (and
(>= (count (edges g2)) (* 2 100 5))
(<= (count (edges g2)) (+ (* 2 100 5) (* 100 5 0.45)))))
(is (and
(<= (- (/ 3 4) 0.5) (clustering-coefficient g2))
(>= (+ (/ 3 4) 0.5) (clustering-coefficient g2)))))
(testing "Construction, Nodes, Edges, Clustering coefficient for g3"
(is loom.graph/graph? g3)
(is (= 100 (count (nodes g2))))
(is (= (count (edges g3)) (* 2 100 6)))
(is (and
(<= (- (/ 3 4) 0.2) (clustering-coefficient g2))
(>= (+ (/ 3 4) 0.2) (clustering-coefficient g2)))))))