/*
* Copyright (c) 2003, the JUNG Project and the Regents of the University
* of California
* All rights reserved.
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* http://jung.sourceforge.net/license.txt for a description.
*/
package edu.uci.ics.jung.algorithms.generators.random;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.apache.commons.collections15.Factory;
import edu.uci.ics.jung.algorithms.generators.EvolvingGraphGenerator;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.MultiGraph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.graph.util.Pair;
/**
* <p>
* Simple evolving scale-free random graph generator. At each time step, a new
* vertex is created and is connected to existing vertices according to the
* principle of "preferential attachment", whereby vertices with higher degree
* have a higher probability of being selected for attachment.
* </p>
*
* <p>
* At a given timestep, the probability <code>p</code> of creating an edge
* between an existing vertex <code>v</code> and the newly added vertex is
*
* <pre>
* p = (degree(v) + 1) / (|E| + |V|);
* </pre>
*
* <p>
* where <code>|E|</code> and <code>|V|</code> are, respectively, the number of
* edges and vertices currently in the network (counting neither the new vertex
* nor the other edges that are being attached to it).
* </p>
*
* <p>
* Note that the formula specified in the original paper (cited below) was
*
* <pre>
* p = degree(v) / |E|
* </pre>
* </p>
*
* <p>
* However, this would have meant that the probability of attachment for any
* existing isolated vertex would be 0. This version uses Lagrangian smoothing
* to give each existing vertex a positive attachment probability.
* </p>
*
* <p>
* The graph created may be either directed or undirected (controlled by a
* constructor parameter); the default is undirected. If the graph is specified
* to be directed, then the edges added will be directed from the newly added
* vertex u to the existing vertex v, with probability proportional to the
* indegree of v (number of edges directed towards v). If the graph is specified
* to be undirected, then the (undirected) edges added will connect u to v, with
* probability proportional to the degree of v.
* </p>
*
* <p>
* The <code>parallel</code> constructor parameter specifies whether parallel
* edges may be created.
* </p>
*
* @see "A.-L. Barabasi and R. Albert, Emergence of scaling in random networks, Science 286, 1999."
* @author Scott White
* @author Joshua O'Madadhain
* @author Tom Nelson - adapted to jung2
*/
public class BarabasiAlbertGenerator<V, E>
implements EvolvingGraphGenerator<V, E> {
private Graph<V, E> mGraph = null;
private int mNumEdgesToAttachPerStep;
private int mElapsedTimeSteps;
private Random mRandom;
protected List<V> vertex_index;
protected int init_vertices;
protected Map<V, Integer> index_vertex;
protected Factory<Graph<V, E>> graphFactory;
protected Factory<V> vertexFactory;
protected Factory<E> edgeFactory;
/**
* Constructs a new instance of the generator.
*
* @param init_vertices
* number of unconnected 'seed' vertices that the graph should
* start with
* @param numEdgesToAttach
* the number of edges that should be attached from the new
* vertex to pre-existing vertices at each time step
* @param directed
* specifies whether the graph and edges to be created should be
* directed or not
* @param parallel
* specifies whether the algorithm permits parallel edges
* @param seed
* random number seed
*/
public BarabasiAlbertGenerator(Factory<Graph<V, E>> graphFactory,
Factory<V> vertexFactory, Factory<E> edgeFactory, int init_vertices,
int numEdgesToAttach, int seed, Set<V> seedVertices) {
assert init_vertices > 0 : "Number of initial unconnected 'seed' vertices "
+ "must be positive";
assert numEdgesToAttach > 0 : "Number of edges to attach "
+ "at each time step must be positive";
mNumEdgesToAttachPerStep = numEdgesToAttach;
mRandom = new Random(seed);
this.graphFactory = graphFactory;
this.vertexFactory = vertexFactory;
this.edgeFactory = edgeFactory;
this.init_vertices = init_vertices;
initialize(seedVertices);
}
/**
* Constructs a new instance of the generator, whose output will be an
* undirected graph, and which will use the current time as a seed for the
* random number generation.
*
* @param init_vertices
* number of vertices that the graph should start with
* @param numEdgesToAttach
* the number of edges that should be attached from the new
* vertex to pre-existing vertices at each time step
*/
public BarabasiAlbertGenerator(Factory<Graph<V, E>> graphFactory,
Factory<V> vertexFactory, Factory<E> edgeFactory, int init_vertices,
int numEdgesToAttach, Set<V> seedVertices) {
this(graphFactory, vertexFactory, edgeFactory, init_vertices,
numEdgesToAttach, (int) System.currentTimeMillis(),
seedVertices);
}
private void initialize(Set<V> seedVertices) {
mGraph = graphFactory.create();
vertex_index = new ArrayList<V>(2 * init_vertices);
index_vertex = new HashMap<V, Integer>(2 * init_vertices);
for (int i = 0; i < init_vertices; i++) {
V v = vertexFactory.create();
mGraph.addVertex(v);
vertex_index.add(v);
index_vertex.put(v, i);
seedVertices.add(v);
}
mElapsedTimeSteps = 0;
}
private void createRandomEdge(V newVertex,
Set<Pair<V>> added_pairs) {
V attach_point;
boolean created_edge = false;
Pair<V> endpoints;
do {
attach_point = vertex_index
.get(mRandom.nextInt(vertex_index.size()));
endpoints = new Pair<V>(newVertex, attach_point);
// if parallel edges are not allowed, skip attach_point if
// <newVertex, attach_point>
// already exists; note that because of the way edges are added, we
// only need to check
// the list of candidate edges for duplicates.
if (!(mGraph instanceof MultiGraph)) {
if (added_pairs.contains(endpoints)) {
continue;
}
if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED
&& added_pairs
.contains(new Pair<V>(attach_point, newVertex))) {
continue;
}
}
double degree = mGraph.inDegree(attach_point);
// subtract 1 from numVertices because we don't want to count
// newVertex
// (which has already been added to the graph, but not to
// vertex_index)
double attach_prob = (degree + 1)
/ (mGraph.getEdgeCount() + mGraph.getVertexCount() - 1);
if (attach_prob >= mRandom.nextDouble()) {
created_edge = true;
}
} while (!created_edge);
added_pairs.add(endpoints);
if (mGraph.getDefaultEdgeType() == EdgeType.UNDIRECTED) {
added_pairs.add(new Pair<V>(attach_point, newVertex));
}
}
@Override
public void evolveGraph(int numTimeSteps) {
for (int i = 0; i < numTimeSteps; i++) {
evolveGraph();
mElapsedTimeSteps++;
}
}
private void evolveGraph() {
V newVertex = vertexFactory.create();
mGraph.addVertex(newVertex);
// generate and store the new edges; don't add them to the graph
// yet because we don't want to bias the degree calculations
// (all new edges in a timestep should be added in parallel)
Set<Pair<V>> added_pairs = new HashSet<Pair<V>>(
mNumEdgesToAttachPerStep * 3);
for (int i = 0; i < mNumEdgesToAttachPerStep; i++) {
createRandomEdge(newVertex, added_pairs);
}
for (Pair<V> pair : added_pairs) {
V v1 = pair.getFirst();
V v2 = pair.getSecond();
if (mGraph.getDefaultEdgeType() != EdgeType.UNDIRECTED
|| !mGraph.isNeighbor(v1, v2)) {
mGraph.addEdge(edgeFactory.create(), pair);
}
}
// now that we're done attaching edges to this new vertex,
// add it to the index
vertex_index.add(newVertex);
index_vertex.put(newVertex, Integer.valueOf(vertex_index.size() - 1));
}
@Override
public int numIterations() {
return mElapsedTimeSteps;
}
@Override
public Graph<V, E> create() {
return mGraph;
}
}