org.jgrapht.alg.scoring.ClosenessCentrality 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.alg.scoring;
import org.jgrapht.*;
import org.jgrapht.alg.interfaces.*;
import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*;
import org.jgrapht.alg.shortestpath.*;
import org.jgrapht.graph.*;
import java.util.*;
/**
* Closeness centrality.
*
*
* Computes the closeness centrality of each vertex of a graph. The closeness of a vertex $x$ is
* defined as the reciprocal of the farness, that is $H(x)= 1 / \sum_{y \neq x} d(x,y)$, where
* $d(x,y)$ is the shortest path distance from $x$ to $y$. When normalization is used, the score is
* multiplied by $n-1$ where $n$ is the total number of vertices in the graph. For more details see
* wikipedia and
*
* - Alex Bavelas. Communication patterns in task-oriented groups. J. Acoust. Soc. Am,
* 22(6):725–730, 1950.
*
*
*
* This implementation computes by default the closeness centrality using outgoing paths and
* normalizes the scores. This behavior can be adjusted by the constructor arguments.
*
*
* When the graph is disconnected, the closeness centrality score equals $0$ for all vertices. In
* the case of weakly connected digraphs, the closeness centrality of several vertices might be 0.
* See {@link HarmonicCentrality} for a different approach in case of disconnected graphs.
*
*
* Shortest paths are computed either by using Dijkstra's algorithm or Floyd-Warshall depending on
* whether the graph has edges with negative edge weights. Thus, the running time is either $O(n (m
* +n \log n))$ or $O(n^3)$ respectively, where $n$ is the number of vertices and $m$ the number of
* edges of the graph.
*
* @param the graph vertex type
* @param the graph edge type
*
* @author Dimitrios Michail
*/
public class ClosenessCentrality
implements VertexScoringAlgorithm
{
/**
* Underlying graph
*/
protected final Graph graph;
/**
* Whether to use incoming or outgoing paths
*/
protected final boolean incoming;
/**
* Whether to normalize scores
*/
protected final boolean normalize;
/**
* The actual scores
*/
protected Map scores;
/**
* Construct a new instance. By default the centrality is normalized and computed using outgoing
* paths.
*
* @param graph the input graph
*/
public ClosenessCentrality(Graph graph)
{
this(graph, false, true);
}
/**
* Construct a new instance.
*
* @param graph the input graph
* @param incoming if true incoming paths are used, otherwise outgoing paths
* @param normalize whether to normalize by multiplying the closeness by $n-1$, where $n$ is the
* number of vertices of the graph
*/
public ClosenessCentrality(Graph graph, boolean incoming, boolean normalize)
{
this.graph = Objects.requireNonNull(graph, "Graph cannot be null");
this.incoming = incoming;
this.normalize = normalize;
this.scores = null;
}
/**
* {@inheritDoc}
*/
@Override
public Map getScores()
{
if (scores == null) {
compute();
}
return Collections.unmodifiableMap(scores);
}
/**
* {@inheritDoc}
*/
@Override
public Double getVertexScore(V v)
{
if (!graph.containsVertex(v)) {
throw new IllegalArgumentException("Cannot return score of unknown vertex");
}
if (scores == null) {
compute();
}
return scores.get(v);
}
/**
* Get the shortest path algorithm for the paths computation.
*
* @return the shortest path algorithm
*/
protected ShortestPathAlgorithm getShortestPathAlgorithm()
{
// setup graph
Graph g;
if (incoming && graph.getType().isDirected()) {
g = new EdgeReversedGraph<>(graph);
} else {
g = graph;
}
// test if we can use Dijkstra
boolean noNegativeWeights = true;
for (E e : g.edgeSet()) {
double w = g.getEdgeWeight(e);
if (w < 0.0) {
noNegativeWeights = false;
break;
}
}
// initialize shortest path algorithm
ShortestPathAlgorithm alg;
if (noNegativeWeights) {
alg = new DijkstraShortestPath<>(g);
} else {
alg = new FloydWarshallShortestPaths<>(g);
}
return alg;
}
/**
* Compute the centrality index
*/
protected void compute()
{
// create result container
this.scores = new HashMap<>();
// initialize shortest path algorithm
ShortestPathAlgorithm alg = getShortestPathAlgorithm();
// compute shortest paths
int n = graph.vertexSet().size();
for (V v : graph.vertexSet()) {
double sum = 0d;
SingleSourcePaths paths = alg.getPaths(v);
for (V u : graph.vertexSet()) {
if (!u.equals(v)) {
sum += paths.getWeight(u);
}
}
if (normalize) {
this.scores.put(v, (n - 1) / sum);
} else {
this.scores.put(v, 1 / sum);
}
}
}
}