/**
*
* Copyright 2013 Davide Nunes Authors : Davide Nunes <davex.pt@gmail.com>
* Website : http://davidenunes.com
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* This file is part of network-api.
*
* network-api is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* The network-api is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* network-api. If not, see <http://www.gnu.org/licenses/gpl.html>.
*/
package org.bhave.network.model.impl;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.math3.random.RandomDataGenerator;
import org.apache.commons.math3.random.RandomGenerator;
import org.bhave.network.api.Network;
import org.bhave.network.api.Node;
import org.bhave.network.model.BAModel;
import com.google.inject.Inject;
import com.google.inject.Provider;
/**
* Efficient implementation of Preferential Attachment for the
* {@link DefaultBAModel}.
*
* @author Davide Nunes
*
*/
public class DefaultBAModel extends AbstractNetworkModel implements BAModel {
private static final String PARAM_NUM_NODES = P_NUM_NODES;
private static final String PARAM_MIN_DEG = P_D;
@Inject
public DefaultBAModel(Configuration config, RandomGenerator random,
Provider<Network> networkProvider) {
super(config, random, networkProvider);
}
@Override
public void generateNetwork() {
// get configuration values
int n = config.getInt(PARAM_NUM_NODES);
int d = config.getInt(PARAM_MIN_DEG);
Node[] nodes = new Node[n];
// add nodes to the network
for (int i = 0; i < n; i++) {
Node newNode = network.createNode();
network.addNode(newNode);
nodes[i] = newNode;
}
// use the existing random number generator to shuffle our nodeArray
RandomDataGenerator randomPerm = new RandomDataGenerator(random);
int[] perm = randomPerm.nextPermutation(n, n);
// from now on instead of using nodes[i] you use nodes[perm[i]] to get
// the node indicated by the permutation
// pool for node scores
int[] scores = new int[n];
int numLinks = 0;
// add 2 nodes
network.addLink(nodes[perm[0]], nodes[perm[1]]);
scores[0] = 1;
scores[1] = 1;
numLinks = 1;
// add the rest of the nodes
for (int v = 2; v < n; v++) {
// current nodes involved
HashSet<Integer> current = new HashSet<>();
int maxLimit = numLinks * 2;
int i = 0;
// add d nodes
for (i = 0; i < d && i < v; i++) {
int tempMaxLimit = maxLimit;
for (Integer e : current) {
tempMaxLimit -= scores[e];
}
int r = random.nextInt(tempMaxLimit);
int p = preferentialAttachment(scores, r, tempMaxLimit, current);
network.addLink(nodes[perm[v]], nodes[perm[p]]);
numLinks++;
current.add(p);
}
scores[v] += i;
for (Integer e : current) {
scores[e]++;
}
}
}
/**
* Select Partner according to a cumulative distribution of the node scores
*
* @param scores node degree scores
* @param r a random number uniformly selected between 0 and the sum of all
* node degrees
* @param maxScore
*
* @param exclude
*
* @return a node according to the preferential attachment mechanism
*/
private int preferentialAttachment(int[] scores, int r, int maxScore,
Set<Integer> exclude) {
int currentScore = 0;
int i = 0;
do {
// skip excluded
if (!exclude.contains(i)) {
currentScore += scores[i];
if (r < currentScore) {
return i;
}
}
i++;
} while (currentScore < maxScore);
return -1;
}
@Override
public void configure(int numNodes, int d, long seed)
throws ConfigurationException {
config.setProperty(PARAM_NUM_NODES, numNodes);
config.setProperty(PARAM_SEED, seed);
config.setProperty(PARAM_MIN_DEG, d);
this.configure(config);
}
@Override
public Configuration getConfiguration() {
return this.config;
}
@Override
public void configure(Configuration configuration)
throws ConfigurationException {
int numNodes = configuration.getInt(PARAM_NUM_NODES);
int d = configuration.getInt(PARAM_MIN_DEG);
if (numNodes < 2 || d < 1) {
throw new ConfigurationException(PARAM_NUM_NODES
+ " must be >= 2 && d >= 1");
}
}
@Override
Configuration defaultConfiguration(Configuration config) {
config.setProperty(PARAM_NUM_NODES, 2);
config.setProperty(PARAM_MIN_DEG, 1);
config.setProperty(PARAM_SEED, System.currentTimeMillis());
return config;
}
}