org.jgrapht.generate.KleinbergSmallWorldGraphGenerator Maven / Gradle / Ivy
/*
* (C) Copyright 2017-2023, by Dimitrios Michail and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.generate;
import org.jgrapht.*;
import org.jgrapht.alg.util.*;
import java.util.*;
/**
* Kleinberg's small-world graph generator.
*
*
* The generator is described in the paper: J. Kleinberg, The Small-World Phenomenon: An Algorithmic
* Perspective, in Proc. 32nd ACM Symp. Theory of Comp., 163-170, 2000.
*
*
* The basic structure is a a two-dimensional grid and allows for edges to be directed. It begins
* with a set of nodes (representing individuals in the social network) that are identified with the
* set of lattice points in an $n \times n$ square. For a universal constant $p \geq 1$, the node
* $u$ has a directed edge to every other node within lattice distance $p$ (these are its local
* contacts). For universal constants $q \geq 0$ and $r \geq 0$, we also construct directed edges
* from $u$ to $q$ other nodes (the long-range contacts) using independent random trials; the i-th
* directed edge from $u$ has endpoint $v$ with probability proportional to \frac{1}{d(u,v)^r}$
* where $d(u,v)$ is the lattice distance from $u$ to $v$.
*
* @author Dimitrios Michail
*
* @param the graph vertex type
* @param the graph edge type
*/
public class KleinbergSmallWorldGraphGenerator
implements GraphGenerator
{
private final Random rng;
private final int n;
private final int p;
private final int q;
private final int r;
/**
* Constructor
*
* @param n generate set of lattice points in a $n$ by $n$ square
* @param p lattice distance for which each node is connected to every other node in the lattice
* (local connections)
* @param q how many long-range contacts to add for each node
* @param r probability distribution parameter which is a basic structural parameter measuring
* how widely "networked" the underlying society of nodes is
* @throws IllegalArgumentException in case of invalid parameters
*/
public KleinbergSmallWorldGraphGenerator(int n, int p, int q, int r)
{
this(n, p, q, r, new Random());
}
/**
* Constructor
*
* @param n generate set of lattice points in a $n$ by $n$ square
* @param p lattice distance for which each node is connected to every other node in the lattice
* (local connections)
* @param q how many long-range contacts to add for each node
* @param r probability distribution parameter which is a basic structural parameter measuring
* how widely "networked" the underlying society of nodes is
* @param seed seed for the random number generator
* @throws IllegalArgumentException in case of invalid parameters
*/
public KleinbergSmallWorldGraphGenerator(int n, int p, int q, int r, long seed)
{
this(n, p, q, r, new Random(seed));
}
/**
* Constructor
*
* @param n generate set of lattice points in a $n \times n$ square
* @param p lattice distance for which each node is connected to every other node in the lattice
* (local connections)
* @param q how many long-range contacts to add for each node
* @param r probability distribution parameter which is a basic structural parameter measuring
* how widely "networked" the underlying society of nodes is
* @param rng the random number generator to use
* @throws IllegalArgumentException in case of invalid parameters
*/
public KleinbergSmallWorldGraphGenerator(int n, int p, int q, int r, Random rng)
{
if (n < 1) {
throw new IllegalArgumentException("parameter n must be positive");
}
this.n = n;
if (p < 1) {
throw new IllegalArgumentException("parameter p must be positive");
}
if (p > 2 * n - 2) {
throw new IllegalArgumentException("lattice distance too large");
}
this.p = p;
if (q < 0) {
throw new IllegalArgumentException("parameter q must be non-negative");
}
this.q = q;
if (r < 0) {
throw new IllegalArgumentException("parameter r must be non-negative");
}
this.r = r;
this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null");
}
/**
* Generates a small-world graph.
*
* @param target the target graph
* @param resultMap not used by this generator, can be null
*/
@Override
public void generateGraph(Graph target, Map resultMap)
{
/*
* Special cases
*/
if (n == 0) {
return;
} else if (n == 1) {
target.addVertex();
return;
}
/*
* Ensure directed or undirected
*/
GraphTests.requireDirectedOrUndirected(target);
boolean isDirected = target.getType().isDirected();
/*
* Create vertices
*/
List nodes = new ArrayList<>(n * n);
for (int i = 0; i < n * n; i++) {
nodes.add(target.addVertex());
}
/*
* Add local-contacts
*/
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
int vi = i * n + j;
V v = nodes.get(vi);
// lookup neighborhood
for (int di = -p; di <= p; di++) {
for (int dj = -p; dj <= p; dj++) {
int t = (i + di) * n + (j + dj);
if (t < 0 || t == vi || t >= n * n) {
continue;
}
if (Math.abs(di) + Math.abs(dj) <= p && (isDirected || t > i * n + j)) {
target.addEdge(v, nodes.get(t));
}
}
}
}
}
/*
* Add long-range contacts
*/
double[] p = new double[n * n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
V v = nodes.get(i * n + j);
/*
* Create inverse r power distribution
*/
double sum = 0d;
for (int oi = 0; oi < n; oi++) {
for (int oj = 0; oj < n; oj++) {
if (oi != i || oj != j) {
double weight = Math.pow(Math.abs(i - oi) + Math.abs(j - oj), -r);
p[oi * n + oj] = weight;
sum += weight;
}
}
}
p[i * n + j] = 0d;
for (int k = 0; k < n * n; k++) {
p[k] /= sum;
}
/*
* Sample from distribution and add long-range edges
*/
AliasMethodSampler sampler = new AliasMethodSampler(p, rng);
for (int k = 0; k < q; k++) {
V u = nodes.get(sampler.next());
if (!u.equals(v) && !target.containsEdge(v, u)) {
target.addEdge(v, u);
}
}
}
}
}
}