All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgrapht.alg.scoring.BetweennessCentrality Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2017-2023, by Assaf Mizrachi 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 java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;

import org.jgrapht.Graph;
import org.jgrapht.Graphs;
import org.jgrapht.alg.interfaces.VertexScoringAlgorithm;
import org.jheaps.AddressableHeap;
import org.jheaps.tree.PairingHeap;

/**
 * Betweenness centrality.
 * 
 * 

* Computes the betweenness centrality of each vertex of a graph. The betweenness centrality of a * node $v$ is given by the expression: $g(v)= \sum_{s \neq v \neq * t}\frac{\sigma_{st}(v)}{\sigma_{st}}$ where $\sigma_{st}$ is the total number of shortest paths * from node $s$ to node $t$ and $\sigma_{st}(v)$ is the number of those paths that pass through * $v$. For more details see * wikipedia. * * The algorithm is based on *

    *
  • Brandes, Ulrik (2001). "A faster algorithm for betweenness centrality". Journal of * Mathematical Sociology. 25 (2): 163–177.
  • *
* * The running time is $O(nm)$ and $O(nm +n^2 \log n)$ for unweighted and weighted graph * respectively, where $n$ is the number of vertices and $m$ the number of edges of the graph. The * space complexity is $O(n + m)$. * * Note that this running time assumes that arithmetic is performed between numbers whose * representation needs a number of bits which is logarithmic in the instance size. There are * instances where this is not true and path counters might grow super exponential. This class * allows the user to adjust whether an exception is thrown in case overflow occurs. Default * behavior is to ignore overflow issues. * * * @param the graph vertex type * @param the graph edge type * * @author Assaf Mizrachi */ public class BetweennessCentrality implements VertexScoringAlgorithm { /** * Underlying graph */ private final Graph graph; /** * Whether to normalize scores */ private final boolean normalize; /** * The actual scores */ private Map scores; /** * Strategy for overflow when counting paths. */ private OverflowStrategy overflowStrategy; /** * Strategy followed when counting paths. */ public enum OverflowStrategy { /** * Do not check for overflow in counters. This means that on certain instances the results * might be wrong due to counters being too large to fit in a long. */ IGNORE_OVERFLOW, /** * An exception is thrown if an overflow in counters is detected. */ THROW_EXCEPTION_ON_OVERFLOW, } /** * Construct a new instance. * * @param graph the input graph */ public BetweennessCentrality(Graph graph) { this(graph, false); } /** * Construct a new instance. * * @param graph the input graph * @param normalize whether to normalize by dividing the closeness by $(n-1) \cdot (n-2)$, where * $n$ is the number of vertices of the graph */ public BetweennessCentrality(Graph graph, boolean normalize) { this(graph, normalize, OverflowStrategy.IGNORE_OVERFLOW); } /** * Construct a new instance. * * @param graph the input graph * @param normalize whether to normalize by dividing the closeness by $(n-1) \cdot (n-2)$, where * $n$ is the number of vertices of the graph * @param overflowStrategy strategy to use if overflow is detected */ public BetweennessCentrality( Graph graph, boolean normalize, OverflowStrategy overflowStrategy) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); this.scores = null; this.normalize = normalize; this.overflowStrategy = overflowStrategy; } /** * {@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); } /** * Compute the centrality index */ private void compute() { // initialize result container scores = new HashMap<>(); graph.vertexSet().forEach(v -> scores.put(v, 0.0)); // compute for each source graph.vertexSet().forEach(this::compute); // For undirected graph, divide scores by two as each shortest path // considered twice. if (!graph.getType().isDirected()) { scores.forEach((v, score) -> scores.put(v, score / 2)); } if (normalize) { int n = graph.vertexSet().size(); int normalizationFactor = (n - 1) * (n - 2); if (normalizationFactor != 0) { scores.forEach((v, score) -> scores.put(v, score / normalizationFactor)); } } } private void compute(V s) { // initialize ArrayDeque stack = new ArrayDeque<>(); Map> predecessors = new HashMap<>(); graph.vertexSet().forEach(w -> predecessors.put(w, new ArrayList<>())); // Number of shortest paths from s to v Map sigma = new HashMap<>(); graph.vertexSet().forEach(t -> sigma.put(t, 0l)); sigma.put(s, 1l); // Distance (Weight) of the shortest path from s to v Map distance = new HashMap<>(); graph.vertexSet().forEach(t -> distance.put(t, Double.POSITIVE_INFINITY)); distance.put(s, 0.0); MyQueue queue = graph.getType().isWeighted() ? new WeightedQueue() : new UnweightedQueue(); queue.insert(s, 0.0); // 1. compute the length and the number of shortest paths between all s to v while (!queue.isEmpty()) { V v = queue.remove(); stack.push(v); for (E e : graph.outgoingEdgesOf(v)) { V w = Graphs.getOppositeVertex(graph, e, v); double eWeight = graph.getEdgeWeight(e); if (eWeight < 0.0) { throw new IllegalArgumentException("Negative edge weight not allowed"); } double d = distance.get(v) + eWeight; // w found for the first time? if (distance.get(w) == Double.POSITIVE_INFINITY) { queue.insert(w, d); distance.put(w, d); sigma.put(w, sigma.get(v)); predecessors.get(w).add(v); } // shortest path to w via v? else if (distance.get(w) == d) { // queue.update(w, d); long wCounter = sigma.get(w); long vCounter = sigma.get(v); long sum = wCounter + vCounter; if (overflowStrategy.equals(OverflowStrategy.THROW_EXCEPTION_ON_OVERFLOW) && sum < 0) { throw new ArithmeticException("long overflow"); } sigma.put(w, sum); predecessors.get(w).add(v); } else if (distance.get(w) > d) { queue.update(w, d); distance.put(w, d); sigma.put(w, sigma.get(v)); predecessors.get(w).clear(); predecessors.get(w).add(v); } } } // 2. sum all pair dependencies. // The pair-dependency of s and v in w Map dependency = new HashMap<>(); graph.vertexSet().forEach(v -> dependency.put(v, 0.0)); // S returns vertices in order of non-increasing distance from s while (!stack.isEmpty()) { V w = stack.pop(); for (V v : predecessors.get(w)) { dependency.put( v, dependency.get(v) + (sigma.get(v).doubleValue() / sigma.get(w).doubleValue()) * (1 + dependency.get(w))); } if (!w.equals(s)) { scores.put(w, scores.get(w) + dependency.get(w)); } } } private interface MyQueue { void insert(T t, D d); void update(T t, D d); T remove(); boolean isEmpty(); } private class WeightedQueue implements MyQueue { AddressableHeap delegate = new PairingHeap<>(); Map> seen = new HashMap<>(); @Override public void insert(V t, Double d) { AddressableHeap.Handle node = delegate.insert(d, t); seen.put(t, node); } @Override public void update(V t, Double d) { if (!seen.containsKey(t)) { throw new IllegalArgumentException("Element " + t + " does not exist in queue"); } seen.get(t).decreaseKey(d); } @Override public V remove() { return delegate.deleteMin().getValue(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } } private class UnweightedQueue implements MyQueue { Queue delegate = new ArrayDeque<>(); @Override public void insert(V t, Double d) { delegate.add(t); } @Override public void update(V t, Double d) { // do nothing } @Override public V remove() { return delegate.remove(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy