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.graph.Graph;
26 import edu.uci.ics.jung.graph.MultiGraph;
27 import edu.uci.ics.jung.graph.util.EdgeType;
28 import edu.uci.ics.jung.graph.util.Pair;
29
30
31 /**
32 * <p>Simple evolving scale-free random graph generator. At each time
33 * step, a new vertex is created and is connected to existing vertices
34 * according to the principle of "preferential attachment", whereby
35 * vertices with higher degree have a higher probability of being
36 * selected for attachment.
37 *
38 * <p>At a given timestep, the probability <code>p</code> of creating an edge
39 * between an existing vertex <code>v</code> and the newly added vertex is
40 * <pre>
41 * p = (degree(v) + 1) / (|E| + |V|);
42 * </pre>
43 *
44 * <p>where <code>|E|</code> and <code>|V|</code> are, respectively, the number
45 * of edges and vertices currently in the network (counting neither the new
46 * vertex nor the other edges that are being attached to it).
47 *
48 * <p>Note that the formula specified in the original paper
49 * (cited below) was
50 * <pre>
51 * p = degree(v) / |E|
52 * </pre>
53 *
54 *
55 * <p>However, this would have meant that the probability of attachment for any existing
56 * isolated vertex would be 0. This version uses Lagrangian smoothing to give
57 * each existing vertex a positive attachment probability.
58 *
59 * <p>The graph created may be either directed or undirected (controlled by a constructor
60 * parameter); the default is undirected.
61 * If the graph is specified to be directed, then the edges added will be directed
62 * from the newly added vertex u to the existing vertex v, with probability proportional to the
63 * indegree of v (number of edges directed towards v). If the graph is specified to be undirected,
64 * then the (undirected) edges added will connect u to v, with probability proportional to the
65 * degree of v.
66 *
67 * <p>The <code>parallel</code> constructor parameter specifies whether parallel edges
68 * may be created.
69 *
70 * @see "A.-L. Barabasi and R. Albert, Emergence of scaling in random networks, Science 286, 1999."
71 * @author Scott White
72 * @author Joshua O'Madadhain
73 * @author Tom Nelson - adapted to jung2
74 */
75 public class BarabasiAlbertGenerator<V,E> implements EvolvingGraphGenerator<V,E> {
76 private Graph<V, E> mGraph = null;
77 private int mNumEdgesToAttachPerStep;
78 private int mElapsedTimeSteps;
79 private Random mRandom;
80 protected List<V> vertex_index;
81 protected int init_vertices;
82 protected Map<V,Integer> index_vertex;
83 protected Supplier<Graph<V,E>> graphFactory;
84 protected Supplier<V> vertexFactory;
85 protected Supplier<E> edgeFactory;
86
87 /**
88 * Constructs a new instance of the generator.
89 *
90 * @param graphFactory factory for graphs of the appropriate type
91 * @param vertexFactory factory for vertices of the appropriate type
92 * @param edgeFactory factory for edges of the appropriate type
93 * @param init_vertices number of unconnected 'seed' vertices that the graph should start with
94 * @param numEdgesToAttach the number of edges that should be attached from the
95 * new vertex to pre-existing vertices at each time step
96 * @param seed random number seed
97 * @param seedVertices storage for the seed vertices that this graph creates
98 */
99 // TODO: seedVertices is a bizarre way of exposing that information, refactor
100 public BarabasiAlbertGenerator(Supplier<Graph<V,E>> graphFactory,
101 Supplier<V> vertexFactory, Supplier<E> edgeFactory,
102 int init_vertices, int numEdgesToAttach,
103 int seed, Set<V> seedVertices)
104 {
105 Preconditions.checkArgument(init_vertices > 0,
106 "Number of initial unconnected 'seed' vertices must be positive");
107 Preconditions.checkArgument(numEdgesToAttach > 0,
108 "Number of edges to attach at each time step must be positive");
109
110 mNumEdgesToAttachPerStep = numEdgesToAttach;
111 mRandom = new Random(seed);
112 this.graphFactory = graphFactory;
113 this.vertexFactory = vertexFactory;
114 this.edgeFactory = edgeFactory;
115 this.init_vertices = init_vertices;
116 initialize(seedVertices);
117 }
118
119
120 /**
121 * Constructs a new instance of the generator, whose output will be an undirected graph,
122 * and which will use the current time as a seed for the random number generation.
123 *
124 * @param graphFactory factory for graphs of the appropriate type
125 * @param vertexFactory factory for vertices of the appropriate type
126 * @param edgeFactory factory for edges of the appropriate type
127 * @param init_vertices number of vertices that the graph should start with
128 * @param numEdgesToAttach the number of edges that should be attached from the
129 * new vertex to pre-existing vertices at each time step
130 * @param seedVertices storage for the seed vertices that this graph creates
131 */
132 public BarabasiAlbertGenerator(Supplier<Graph<V,E>> graphFactory,
133 Supplier<V> vertexFactory, Supplier<E> edgeFactory,
134 int init_vertices, int numEdgesToAttach, Set<V> seedVertices) {
135 this(graphFactory, vertexFactory, edgeFactory, init_vertices, numEdgesToAttach, (int) System.currentTimeMillis(), seedVertices);
136 }
137
138 private void initialize(Set<V> seedVertices) {
139
140 mGraph = graphFactory.get();
141
142 vertex_index = new ArrayList<V>(2*init_vertices);
143 index_vertex = new HashMap<V, Integer>(2*init_vertices);
144 for (int i = 0; i < init_vertices; i++) {
145 V v = vertexFactory.get();
146 mGraph.addVertex(v);
147 vertex_index.add(v);
148 index_vertex.put(v, i);
149 seedVertices.add(v);
150 }
151
152 mElapsedTimeSteps = 0;
153 }
154
155 private void createRandomEdge(Collection<V> preexistingNodes,
156 V newVertex, Set<Pair<V>> added_pairs) {
157 V attach_point;
158 boolean created_edge = false;
159 Pair<V> endpoints;
160 do {
161 attach_point = vertex_index.get(mRandom.nextInt(vertex_index.size()));
162
163 endpoints = new Pair<V>(newVertex, attach_point);
164
165 // if parallel edges are not allowed, skip attach_point if <newVertex, attach_point>
166 // already exists; note that because of the way edges are added, we only need to check
167 // the list of candidate edges for duplicates.
168 if (!(mGraph instanceof MultiGraph))
169 {
170 if (added_pairs.contains(endpoints))
171 continue;
172 if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED &&
173 added_pairs.contains(new Pair<V>(attach_point, newVertex)))
174 continue;
175 }
176
177 double degree = mGraph.inDegree(attach_point);
178
179 // subtract 1 from numVertices because we don't want to count newVertex
180 // (which has already been added to the graph, but not to vertex_index)
181 double attach_prob = (degree + 1) / (mGraph.getEdgeCount() + mGraph.getVertexCount() - 1);
182 if (attach_prob >= mRandom.nextDouble())
183 created_edge = true;
184 }
185 while (!created_edge);
186
187 added_pairs.add(endpoints);
188
189 if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED) {
190 added_pairs.add(new Pair<V>(attach_point, newVertex));
191 }
192 }
193
194 public void evolveGraph(int numTimeSteps) {
195
196 for (int i = 0; i < numTimeSteps; i++) {
197 evolveGraph();
198 mElapsedTimeSteps++;
199 }
200 }
201
202 private void evolveGraph() {
203 Collection<V> preexistingNodes = mGraph.getVertices();
204 V newVertex = vertexFactory.get();
205
206 mGraph.addVertex(newVertex);
207
208 // generate and store the new edges; don't add them to the graph
209 // yet because we don't want to bias the degree calculations
210 // (all new edges in a timestep should be added in parallel)
211 Set<Pair<V>> added_pairs = new HashSet<Pair<V>>(mNumEdgesToAttachPerStep*3);
212
213 for (int i = 0; i < mNumEdgesToAttachPerStep; i++)
214 createRandomEdge(preexistingNodes, newVertex, added_pairs);
215
216 for (Pair<V> pair : added_pairs)
217 {
218 V v1 = pair.getFirst();
219 V v2 = pair.getSecond();
220 if (mGraph.getDefaultEdgeType() != EdgeType.UNDIRECTED ||
221 !mGraph.isNeighbor(v1, v2))
222 mGraph.addEdge(edgeFactory.get(), pair);
223 }
224 // now that we're done attaching edges to this new vertex,
225 // add it to the index
226 vertex_index.add(newVertex);
227 index_vertex.put(newVertex, new Integer(vertex_index.size() - 1));
228 }
229
230 public int numIterations() {
231 return mElapsedTimeSteps;
232 }
233
234 public Graph<V, E> get() {
235 return mGraph;
236 }
237 }