View Javadoc
1   /*
2    * Copyright (c) 2003, The JUNG Authors 
3    *
4    * All rights reserved.
5    *
6    * This software is open-source under the BSD license; see either
7    * "license.txt" or
8    * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
9    */
10  package edu.uci.ics.jung.algorithms.generators.random;
11  
12  import java.util.ArrayList;
13  import java.util.Collection;
14  import java.util.HashMap;
15  import java.util.HashSet;
16  import java.util.List;
17  import java.util.Map;
18  import java.util.Random;
19  import java.util.Set;
20  
21  import com.google.common.base.Preconditions;
22  import com.google.common.base.Supplier;
23  
24  import edu.uci.ics.jung.algorithms.generators.EvolvingGraphGenerator;
25  import edu.uci.ics.jung.algorithms.util.WeightedChoice;
26  import edu.uci.ics.jung.graph.Graph;
27  import edu.uci.ics.jung.graph.MultiGraph;
28  import edu.uci.ics.jung.graph.util.EdgeType;
29  import edu.uci.ics.jung.graph.util.Pair;
30  
31  /**
32   * <p>
33   * Simple evolving scale-free random graph generator. At each time step, a new
34   * vertex is created and is connected to existing vertices according to the
35   * principle of "preferential attachment", whereby vertices with higher degree
36   * have a higher probability of being selected for attachment.
37   * 
38   * <p>
39   * At a given timestep, the probability <code>p</code> of creating an edge
40   * between an existing vertex <code>v</code> and the newly added vertex is
41   * 
42   * <pre>
43   * p = (degree(v) + 1) / (|E| + |V|);
44   * </pre>
45   * 
46   * <p>
47   * where <code>|E|</code> and <code>|V|</code> are, respectively, the number of
48   * edges and vertices currently in the network (counting neither the new vertex
49   * nor the other edges that are being attached to it).
50   * 
51   * <p>
52   * Note that the formula specified in the original paper (cited below) was
53   * 
54   * <pre>
55   * p = degree(v) / |E|
56   * </pre>
57   * 
58   * 
59   * <p>
60   * However, this would have meant that the probability of attachment for any
61   * existing isolated vertex would be 0. This version uses Lagrangian smoothing
62   * to give each existing vertex a positive attachment probability.
63   * 
64   * <p>
65   * The graph created may be either directed or undirected (controlled by a
66   * constructor parameter); the default is undirected. If the graph is specified
67   * to be directed, then the edges added will be directed from the newly added
68   * vertex u to the existing vertex v, with probability proportional to the
69   * indegree of v (number of edges directed towards v). If the graph is specified
70   * to be undirected, then the (undirected) edges added will connect u to v, with
71   * probability proportional to the degree of v.
72   * 
73   * <p>
74   * The <code>parallel</code> constructor parameter specifies whether parallel
75   * edges may be created.
76   * 
77   * @see "A.-L. Barabasi and R. Albert, Emergence of scaling in random networks, Science 286, 1999."
78   * @author Scott White
79   * @author Joshua O'Madadhain
80   * @author Tom Nelson - adapted to jung2
81   * @author James Marchant
82   */
83  public class BarabasiAlbertGenerator<V, E> implements EvolvingGraphGenerator<V, E> {
84  	private Graph<V, E> mGraph = null;
85  	private int mNumEdgesToAttachPerStep;
86  	private int mElapsedTimeSteps;
87  	private Random mRandom;
88  	protected List<V> vertex_index;
89  	protected int init_vertices;
90  	protected Map<V, Integer> index_vertex;
91  	protected Supplier<Graph<V, E>> graphFactory;
92  	protected Supplier<V> vertexFactory;
93  	protected Supplier<E> edgeFactory;
94  
95  	/**
96  	 * Constructs a new instance of the generator.
97  	 * 
98  	 * @param graphFactory
99  	 *            factory for graphs of the appropriate type
100 	 * @param vertexFactory
101 	 *            factory for vertices of the appropriate type
102 	 * @param edgeFactory
103 	 *            factory for edges of the appropriate type
104 	 * @param init_vertices
105 	 *            number of unconnected 'seed' vertices that the graph should
106 	 *            start with
107 	 * @param numEdgesToAttach
108 	 *            the number of edges that should be attached from the new
109 	 *            vertex to pre-existing vertices at each time step
110 	 * @param seed
111 	 *            random number seed
112 	 * @param seedVertices
113 	 *            storage for the seed vertices that this graph creates
114 	 */
115 	// TODO: seedVertices is a bizarre way of exposing that information,
116 	// refactor
117 	public BarabasiAlbertGenerator(Supplier<Graph<V, E>> graphFactory, Supplier<V> vertexFactory,
118 			Supplier<E> edgeFactory, int init_vertices, int numEdgesToAttach, int seed, Set<V> seedVertices) {
119 		Preconditions.checkArgument(init_vertices > 0,
120 				"Number of initial unconnected 'seed' vertices must be positive");
121 		Preconditions.checkArgument(numEdgesToAttach > 0,
122 				"Number of edges to attach at each time step must be positive");
123 		Preconditions.checkArgument(numEdgesToAttach <= init_vertices,
124 				"Number of edges to attach at each time step must less than or equal to the number of initial vertices");
125 
126 		mNumEdgesToAttachPerStep = numEdgesToAttach;
127 		mRandom = new Random(seed);
128 		this.graphFactory = graphFactory;
129 		this.vertexFactory = vertexFactory;
130 		this.edgeFactory = edgeFactory;
131 		this.init_vertices = init_vertices;
132 		initialize(seedVertices);
133 	}
134 
135 	/**
136 	 * Constructs a new instance of the generator, whose output will be an
137 	 * undirected graph, and which will use the current time as a seed for the
138 	 * random number generation.
139 	 * 
140 	 * @param graphFactory
141 	 *            factory for graphs of the appropriate type
142 	 * @param vertexFactory
143 	 *            factory for vertices of the appropriate type
144 	 * @param edgeFactory
145 	 *            factory for edges of the appropriate type
146 	 * @param init_vertices
147 	 *            number of vertices that the graph should start with
148 	 * @param numEdgesToAttach
149 	 *            the number of edges that should be attached from the new
150 	 *            vertex to pre-existing vertices at each time step
151 	 * @param seedVertices
152 	 *            storage for the seed vertices that this graph creates
153 	 */
154 	public BarabasiAlbertGenerator(Supplier<Graph<V, E>> graphFactory, Supplier<V> vertexFactory,
155 			Supplier<E> edgeFactory, int init_vertices, int numEdgesToAttach, Set<V> seedVertices) {
156 		this(graphFactory, vertexFactory, edgeFactory, init_vertices, numEdgesToAttach,
157 				(int) System.currentTimeMillis(), seedVertices);
158 	}
159 
160 	private void initialize(Set<V> seedVertices) {
161 		mGraph = graphFactory.get();
162 
163 		vertex_index = new ArrayList<V>(2 * init_vertices);
164 		index_vertex = new HashMap<V, Integer>(2 * init_vertices);
165 		for (int i = 0; i < init_vertices; i++) {
166 			V v = vertexFactory.get();
167 			mGraph.addVertex(v);
168 			vertex_index.add(v);
169 			index_vertex.put(v, i);
170 			seedVertices.add(v);
171 		}
172 
173 		mElapsedTimeSteps = 0;
174 	}
175 
176 	public void evolveGraph(int numTimeSteps) {
177 
178 		for (int i = 0; i < numTimeSteps; i++) {
179 			evolveGraph();
180 			mElapsedTimeSteps++;
181 		}
182 	}
183 
184 	private void evolveGraph() {
185 		Collection<V> preexistingNodes = mGraph.getVertices();
186 		V newVertex = vertexFactory.get();
187 
188 		mGraph.addVertex(newVertex);
189 
190 		// generate and store the new edges; don't add them to the graph
191 		// yet because we don't want to bias the degree calculations
192 		// (all new edges in a timestep should be added in parallel)
193 		Set<Pair<V>> added_pairs = createRandomEdges(preexistingNodes, newVertex, mNumEdgesToAttachPerStep);
194 
195 		for (Pair<V> pair : added_pairs) {
196 			V v1 = pair.getFirst();
197 			V v2 = pair.getSecond();
198 			if (mGraph.getDefaultEdgeType() != EdgeType.UNDIRECTED || !mGraph.isNeighbor(v1, v2))
199 				mGraph.addEdge(edgeFactory.get(), pair);
200 		}
201 		// now that we're done attaching edges to this new vertex,
202 		// add it to the index
203 		vertex_index.add(newVertex);
204 		index_vertex.put(newVertex, new Integer(vertex_index.size() - 1));
205 	}
206 
207 	private Set<Pair<V>> createRandomEdges(Collection<V> preexistingNodes, V newVertex, int numEdges) {
208 		Set<Pair<V>> added_pairs = new HashSet<Pair<V>>(numEdges * 3);
209 
210 		/* Generate the probability distribution */
211 		Map<V, Double> item_weights = new HashMap<V, Double>();
212 		for (V v : preexistingNodes) {
213 			/*
214 			 * as preexistingNodes is a view onto the vertex set, it will
215 			 * contain the new node. In the construction of Barabasi-Albert,
216 			 * there should be no self-loops.
217 			 */
218 			if (v == newVertex)
219 				continue;
220 
221 			double degree;
222 			double denominator;
223 
224 			/*
225 			 * Attachment probability is dependent on whether the graph is
226 			 * directed or undirected.
227 			 * 
228 			 * Subtract 1 from numVertices because we don't want to count
229 			 * newVertex (which has already been added to the graph, but not to
230 			 * vertex_index).
231 			 */
232 			if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED) {
233 				degree = mGraph.degree(v);
234 				denominator = (2 * mGraph.getEdgeCount()) + mGraph.getVertexCount() - 1;
235 			} else {
236 				degree = mGraph.inDegree(v);
237 				denominator = mGraph.getEdgeCount() + mGraph.getVertexCount() - 1;
238 			}
239 
240 			double prob = (degree + 1) / denominator;
241 			item_weights.put(v, prob);
242 		}
243 		WeightedChoice<V> nodeProbabilities = new WeightedChoice<V>(item_weights, mRandom);
244 
245 		for (int i = 0; i < numEdges; i++) {
246 			createRandomEdge(preexistingNodes, newVertex, added_pairs, nodeProbabilities);
247 		}
248 
249 		return added_pairs;
250 	}
251 
252 	private void createRandomEdge(Collection<V> preexistingNodes, V newVertex, Set<Pair<V>> added_pairs,
253 			WeightedChoice<V> weightedProbabilities) {
254 		V attach_point;
255 		boolean created_edge = false;
256 		Pair<V> endpoints;
257 
258 		do {
259 			attach_point = weightedProbabilities.nextItem();
260 
261 			endpoints = new Pair<V>(newVertex, attach_point);
262 
263 			/*
264 			 * If parallel edges are not allowed, skip attach_point if
265 			 * <newVertex, attach_point> already exists; note that because of
266 			 * the way the new node's edges are added, we only need to check the
267 			 * list of candidate edges for duplicates.
268 			 */
269 			if (!(mGraph instanceof MultiGraph)) {
270 				if (added_pairs.contains(endpoints))
271 					continue;
272 				if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED
273 						&& added_pairs.contains(new Pair<V>(attach_point, newVertex)))
274 					continue;
275 			}
276 			created_edge = true;
277 		} while (!created_edge);
278 
279 		added_pairs.add(endpoints);
280 
281 		if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED) {
282 			added_pairs.add(new Pair<V>(attach_point, newVertex));
283 		}
284 	}
285 
286 	public int numIterations() {
287 		return mElapsedTimeSteps;
288 	}
289 
290 	public Graph<V, E> get() {
291 		return mGraph;
292 	}
293 }