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

org.jgrapht.alg.densesubgraph.GoldbergMaximumDensitySubgraphAlgorithmBase Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2018-2023, by Andre Immig 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.densesubgraph;

import org.jgrapht.*;
import org.jgrapht.alg.interfaces.*;
import org.jgrapht.graph.*;
import org.jgrapht.graph.builder.*;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

/**
 * This abstract base class computes a maximum density subgraph based on the algorithm described by
 * Andrew Vladislav Goldberg in
 *  Finding Maximum
 * Density Subgraphs, 1984, University of Berkley. Each subclass decides which concrete
 * definition of density is used by implementing {@link #getEdgeWeightFromSourceToVertex(Object)}
 * and {@link #getEdgeWeightFromVertexToSink(Object)} as proposed in the paper. After the
 * computation the density is computed using {@link MaximumDensitySubgraphAlgorithm#getDensity()}.
 * 
* The basic concept is to construct a network that can be used to compute the maximum density * subgraph using a binary search approach. *

* In the simplest case of an unweighted graph $G=(V,E)$ the density of $G$ can be defined to be * \[\frac{\left|{E}\right|}{\left|{V}\right|}\], where a directed graph can be considered as * undirected. Therefore it is in this case equal to half the average vertex degree. This variant is * implemented in {@link GoldbergMaximumDensitySubgraphAlgorithm}; because the following math * translates directly to other variants the full math is only fully explained once. *

* The idea of the algorithm is to construct a network based on the input graph $G=(V,E)$ and some * guess $g$ for the density. This network $N=(V_N, E_N)$ is constructed as follows: \[V_N=V\cup * {s,t}\] \[E_N=\{(i,j)| \{i,j\} \in E\} \cup \{(s,i)| i\in V\} \cup \{(i,t)| i \in V\}\]
* Additionally one defines the following weights for the network: \[c_{ij}=1 \forall \{i,j\}\in E\] * \[c_{si}=m \forall i \in V\] \[c_{it}=m+2g-d_i \forall i \in V\] where $m=\left|{E}\right|$ and * $d_i$ is the degree of vertex $i$.
* As seen later these weights depend on the definition of the density. Therefore these weights and * the following applies to the definition of density from above. Definitions suitable for other * cases in can be found in the corresponding subclasses. *

* Using this network one can show some important properties, that are essential for the algorithm * to work. The capacity of a s-t of N is given by: \[C(S,T) = m\left|{V}\right| + * 2\left|{V_1}\right|\left(g - D_1\right)\] where $V_1 \dot{\cup} V_2=V$ and $V_1 = S\setminus * \{s\}, V_2= T\setminus \{t\}$ and $D_1$ shall be the density of the induced subgraph of $V_1$ * regarding $G$. *

*

* Especially important is the capacity of minimum s-t Cut. Using the above equation, one can derive * that given a minimum s-t Cut of $N$ and the maximum density of $G$ to be $D$, then $g\geq D$ if * $V_1=\emptyset$,otherwise $g\leq D$. Moreover the induced subgraph of $V_1$ regarding G is * guaranteed to have density greater $g$, otherwise it can be used to proof that there can't exist * any subgraph of $G$ greater $g$. Based on this property one can use a binary search approach to * shrink the possible interval which contains the solution. *

*

* Because the density is per definition guaranteed to be rational, the distance of 2 possible * solutions for the maximum density can't be smaller than $\frac{1}{n(n-1)}$. This means shrinking * the binary search interval to this size, the correct solution is found. The runtime can in this * case be given by $O(M(n,n+m)\log{n}$, where $M(n,m)$ is the runtime of the internally used * {@link MinimumSTCutAlgorithm}. Especially for large networks it is advised to use a * {@link MinimumSTCutAlgorithm} whose runtime doesn't depend on the number of edges, because the * network $N$ has $O(n+m)$ edges. Preferably one should use * {@link org.jgrapht.alg.flow.PushRelabelMFImpl}, leading to a runtime of $O(n^{3}\log{n})$. *

*

* Similar to the above explanation the same argument can be applied for other definitions of * density by adapting the definitions and the network accordingly. Some generalizations can be * found in the paper. As these more general variants including edge weights are only guaranteed to * terminate for integer edge weights, instead of using the natural termination property, the * algorithm needs to be called with $\varepsilon$ in the constructor. The computation then ensures, * that the returned maximum density only differs at most $\varepsilon$ from the correct solution. * This is why subclasses of this class might have a little different runtime analysis regarding the * $\log{n}$ part. *

* * @param Type of vertices * @param Type of edges * * @author Andre Immig */ public abstract class GoldbergMaximumDensitySubgraphAlgorithmBase implements MaximumDensitySubgraphAlgorithm { private double lower, upper, epsilon; protected double guess; protected final Graph graph; private Graph densestSubgraph; private Graph currentNetwork; private Set currentVertices; private V s, t; private MinimumSTCutAlgorithm minSTCutAlg; private boolean checkWeights; /** * Constructor * * @param graph input for computation * @param s additional source vertex * @param t additional target vertex * @param checkWeights if true implementation will enforce all internal weights to be positive * @param epsilon to use for internal computation * @param algFactory function to construct the subalgorithm */ public GoldbergMaximumDensitySubgraphAlgorithmBase( Graph graph, V s, V t, boolean checkWeights, double epsilon, Function, MinimumSTCutAlgorithm> algFactory) { if (graph.containsVertex(s) || graph.containsVertex(t)) { throw new IllegalArgumentException("Source or sink vertex already in graph"); } this.s = Objects.requireNonNull(s, "Source vertex is null"); this.t = Objects.requireNonNull(t, "Sink vertex is null"); this.graph = Objects.requireNonNull(graph, "Graph is null"); this.epsilon = epsilon; this.guess = 0; this.lower = 0; this.upper = this.computeDensityNumerator(this.graph); this.checkWeights = checkWeights; this.currentNetwork = this.buildNetwork(); this.currentVertices = new HashSet<>(); this.initializeNetwork(); this.checkForEmptySolution(); this.minSTCutAlg = algFactory.apply(currentNetwork); } /** * Helper method for constructing the internally used network */ private Graph buildNetwork() { return GraphTypeBuilder . directed().allowingMultipleEdges(true).allowingSelfLoops(true) .weighted(true).edgeSupplier(DefaultWeightedEdge::new).buildGraph(); } /** * Updates network for next computation, e.g edges from v to t and from s to v Enforces * positivity on network weights if specified by adding subtracting the lowest weights to all * edges $(v,t)$ and $(v,s)$. **/ private void updateNetwork() { for (V v : this.graph.vertexSet()) { currentNetwork .setEdgeWeight(currentNetwork.getEdge(v, t), getEdgeWeightFromVertexToSink(v)); currentNetwork .setEdgeWeight(currentNetwork.getEdge(s, v), getEdgeWeightFromSourceToVertex(v)); } if (this.checkWeights) { double minCapacity = getMinimalCapacity(); if (minCapacity < 0) { DefaultWeightedEdge e; for (V v : this.graph.vertexSet()) { e = currentNetwork.getEdge(v, t); currentNetwork.setEdgeWeight(e, currentNetwork.getEdgeWeight(e) - minCapacity); e = currentNetwork.getEdge(s, v); currentNetwork.setEdgeWeight(e, currentNetwork.getEdgeWeight(e) - minCapacity); } } } } /** * @return the minimal capacity of all edges vt and sv */ private double getMinimalCapacity() { DoubleStream sourceWeights = this.graph.vertexSet().stream().mapToDouble( v -> currentNetwork.getEdgeWeight(currentNetwork.getEdge(v, t))); DoubleStream sinkWeights = this.graph.vertexSet().stream().mapToDouble( v -> currentNetwork.getEdgeWeight(currentNetwork.getEdge(s, v))); OptionalDouble min = DoubleStream.concat(sourceWeights, sinkWeights).min(); return min.isPresent() ? min.getAsDouble() : 0; } /** * Initializes network (only once) for Min-Cut computation Adds s,t to vertex set Adds every v * in V to vertex set Adds edge sv and vt for each v in V to edge set Adds every edge uv and vu * from E to edge set Sets edge weights for all edges from E */ private void initializeNetwork() { currentNetwork.addVertex(s); currentNetwork.addVertex(t); for (V v : this.graph.vertexSet()) { currentNetwork.addVertex(v); currentNetwork.addEdge(s, v); currentNetwork.addEdge(v, t); } for (E e : this.graph.edgeSet()) { DefaultWeightedEdge e1 = currentNetwork.addEdge(this.graph.getEdgeSource(e), this.graph.getEdgeTarget(e)); DefaultWeightedEdge e2 = currentNetwork.addEdge(this.graph.getEdgeTarget(e), this.graph.getEdgeSource(e)); double weight = this.graph.getEdgeWeight(e); currentNetwork.setEdgeWeight(e1, weight); currentNetwork.setEdgeWeight(e2, weight); } } /** * Algorithm to compute max density subgraph Performs binary search on the initial interval * lower-upper until interval is smaller than epsilon In case no solution is found because * epsilon is too big, the computation continues until a (first) solution is found, thereby * avoiding to return an empty graph. * * @return max density subgraph of the graph */ public Graph calculateDensest() { if (this.densestSubgraph != null) { return this.densestSubgraph; } Set sourcePartition; while (Double.compare(upper - lower, this.epsilon) >= 0) { guess = lower + ((upper - lower)) / 2; updateNetwork(); minSTCutAlg.calculateMinCut(s, t); sourcePartition = minSTCutAlg.getSourcePartition(); sourcePartition.remove(s); if (sourcePartition.isEmpty()) { upper = guess; } else { lower = guess; currentVertices = new HashSet<>(sourcePartition); } } this.densestSubgraph = new AsSubgraph<>(graph, currentVertices); return this.densestSubgraph; } /** * Computes density of a maximum density subgraph. * * @return the actual density of the maximum density subgraph */ public double getDensity() { if (this.densestSubgraph == null) { this.calculateDensest(); } double denominator = computeDensityDenominator(this.densestSubgraph); if (denominator != 0) { return computeDensityNumerator(this.densestSubgraph) / denominator; } return 0; } /** * Getter for network weights of edges su for u in V * * @param vertex of V * @return weight of the edge (s,v) */ protected abstract double getEdgeWeightFromSourceToVertex(V vertex); /** * Getter for network weights of edges ut for u in V * * @param vertex of V * @return weight of the edge (v,t) */ protected abstract double getEdgeWeightFromVertexToSink(V vertex); /** * @param g the graph to compute the numerator density from * @return numerator part of the density */ protected abstract double computeDensityNumerator(Graph g); /** * @param g the graph to compute the denominator density from * @return numerator part of the density */ protected abstract double computeDensityDenominator(Graph g); /** * Check if denominator will be empty to avoid dividing by 0. */ private void checkForEmptySolution() { if (Double.compare(computeDensityDenominator(this.graph), 0) == 0) { this.densestSubgraph = new AsSubgraph<>(this.graph, null); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy