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

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

The newest version!
/*
 * (C) Copyright 2020-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 java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.jgrapht.Graph;
import org.jgrapht.GraphTests;
import org.jgrapht.Graphs;
import org.jgrapht.alg.interfaces.EdgeScoringAlgorithm;
import org.jheaps.AddressableHeap;
import org.jheaps.AddressableHeap.Handle;
import org.jheaps.tree.PairingHeap;

/**
 * Edge betweenness centrality.
 * 
 * 

* A natural extension of betweenness to edges by counting the total shortest paths that pass * through an edge. See the paper: Ulrik Brandes: On Variants of Shortest-Path Betweenness * Centrality and their Generic Computation. Social Networks 30(2):136-145, 2008, for a nice * discussion of different variants of betweenness centrality. Note that this implementation does * not work for graphs which have multiple edges. Self-loops do not influence the result and are * thus ignored. * *

* This implementation allows the user to compute centrality contributions only from a subset of the * graph vertices, i.e. to start shortest path computations only from a subset of the vertices. This * allows centrality approximations in big graphs. Note that in this case, the user is responsible * for any normalization necessary due to duplicate shortest paths that might occur in undirected * graphs. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class EdgeBetweennessCentrality implements EdgeScoringAlgorithm { private final Graph graph; private final Iterable startVertices; private final boolean divideByTwo; private Map scores; private final 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 EdgeBetweennessCentrality(Graph graph) { this(graph, OverflowStrategy.IGNORE_OVERFLOW, null); } /** * Construct a new instance. * * @param graph the input graph * @param overflowStrategy strategy to use if overflow is detected */ public EdgeBetweennessCentrality(Graph graph, OverflowStrategy overflowStrategy) { this(graph, overflowStrategy, null); } /** * Construct a new instance. * * @param graph the input graph * @param overflowStrategy strategy to use if overflow is detected * @param startVertices vertices from which to start shortest path computations. This parameter * allows the user to compute edge centrality contributions only from a subset of the * vertices of the graph. If null the whole graph vertex set is used. */ public EdgeBetweennessCentrality( Graph graph, OverflowStrategy overflowStrategy, Iterable startVertices) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); if (GraphTests.hasMultipleEdges(graph)) { throw new IllegalArgumentException("Graphs with multiple edges not supported"); } this.scores = null; this.overflowStrategy = overflowStrategy; if (startVertices == null) { this.startVertices = graph.vertexSet(); // divide by two only if all pairs are used this.divideByTwo = graph.getType().isUndirected(); } else { this.startVertices = startVertices; // the user is responsible for duplicate shortest paths this.divideByTwo = false; } } @Override public Map getScores() { if (scores == null) { scores = graph.getType().isWeighted() ? new WeightedAlgorithm().getScores() : new Algorithm().getScores(); } return Collections.unmodifiableMap(scores); } @Override public Double getEdgeScore(E e) { if (!graph.containsEdge(e)) { throw new IllegalArgumentException("Cannot return score of unknown edge"); } if (scores == null) { scores = graph.getType().isWeighted() ? new WeightedAlgorithm().getScores() : new Algorithm().getScores(); } return scores.get(e); } /* * The basic algorithm */ private class Algorithm { protected Map scores = new HashMap<>(); protected Deque stack = new ArrayDeque<>(); public Map getScores() { for (E e : graph.iterables().edges()) { scores.put(e, 0d); } for (V v : startVertices) { singleVertexUpdate(v); } if (divideByTwo) { scores.forEach((e, score) -> scores.put(e, score / 2d)); } return scores; } protected void singleVertexUpdate(V source) { // initialization Map> pred = new HashMap<>(); Map dist = new HashMap<>(); Map sigma = new HashMap<>(); Deque queue = new ArrayDeque<>(); for (V v : graph.vertexSet()) { sigma.put(v, 0l); } sigma.put(source, 1l); dist.put(source, 0d); queue.add(source); // main loop while (!queue.isEmpty()) { V v = queue.remove(); stack.push(v); double vDistance = dist.get(v); for (E e : graph.outgoingEdgesOf(v)) { V w = Graphs.getOppositeVertex(graph, e, v); if (w.equals(v)) { // ignore self-loops continue; } // path discovery if (!dist.containsKey(w)) { dist.put(w, vDistance + 1d); queue.add(w); } // path counting double wDistance = dist.get(w); if (Double.compare(wDistance, vDistance + 1d) == 0) { 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); pred.computeIfAbsent(w, k -> new ArrayList<>()).add(e); } } } // accumulation accumulate(pred, sigma); } protected void accumulate(Map> pred, Map sigma) { Map delta = new HashMap<>(); for (V v : graph.iterables().vertices()) { delta.put(v, 0d); } while (!stack.isEmpty()) { V w = stack.pop(); List wPred = pred.get(w); if (wPred != null) { for (E e : wPred) { V v = Graphs.getOppositeVertex(graph, e, w); double c = (sigma.get(v).doubleValue() / sigma.get(w).doubleValue()) * (1 + delta.get(w)); scores.put(e, scores.get(e) + c); delta.put(v, delta.get(v) + c); } } } } } /* * Weighted variant where shortest paths are computed using edge weights. */ private class WeightedAlgorithm extends Algorithm { @Override protected void singleVertexUpdate(V source) { // initialization Map> pred = new HashMap<>(); Map> dist = new HashMap<>(); Map sigma = new HashMap<>(); AddressableHeap heap = new PairingHeap<>(); for (V v : graph.vertexSet()) { sigma.put(v, 0l); } sigma.put(source, 1l); dist.put(source, heap.insert(0d, source)); // main loop while (!heap.isEmpty()) { Handle vHandle = heap.deleteMin(); V v = vHandle.getValue(); double vDistance = vHandle.getKey(); stack.push(v); for (E e : graph.outgoingEdgesOf(v)) { V w = Graphs.getOppositeVertex(graph, e, v); if (w.equals(v)) { // ignore self-loops continue; } double eWeight = graph.getEdgeWeight(e); if (eWeight < 0d) { throw new IllegalArgumentException("Negative edge weights are not allowed"); } double newDistance = vDistance + eWeight; // path discovery Handle wHandle = dist.get(w); if (wHandle == null) { wHandle = heap.insert(newDistance, w); dist.put(w, wHandle); sigma.put(w, 0l); pred.put(w, new ArrayList<>()); } else if (Double.compare(wHandle.getKey(), newDistance) > 0) { wHandle.decreaseKey(newDistance); sigma.put(w, 0l); pred.put(w, new ArrayList<>()); } // path counting if (Double.compare(wHandle.getKey(), newDistance) == 0) { 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); pred.computeIfAbsent(w, k -> new ArrayList<>()).add(e); } } } // accumulation accumulate(pred, sigma); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy