org.nlpub.watset.graph.MaxMax Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of watset Show documentation
Show all versions of watset Show documentation
An open source implementation of the Watset algorithm for fuzzy graph clustering.
The newest version!
/*
* Copyright 2019 Dmitry Ustalov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.nlpub.watset.graph;
import org.jgrapht.Graph;
import org.jgrapht.Graphs;
import org.jgrapht.alg.interfaces.ClusteringAlgorithm;
import org.jgrapht.graph.AsUnmodifiableGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleDirectedGraph;
import java.util.*;
import static java.util.Objects.isNull;
import static org.jgrapht.GraphTests.*;
/**
* Implementation of the MaxMax soft clustering algorithm.
*
* @param the type of nodes in the graph
* @param the type of edges in the graph
* @see Hope & Keller (CICLing 2013)
*/
public class MaxMax implements ClusteringAlgorithm {
/**
* Builder for {@link MaxMax}.
*
* @param the type of nodes in the graph
* @param the type of edges in the graph
*/
@SuppressWarnings({"unused", "UnusedReturnValue"})
public static class Builder implements ClusteringAlgorithmBuilder> {
@Override
public MaxMax apply(Graph graph) {
return new MaxMax<>(graph);
}
}
/**
* Create a builder.
*
* @param the type of nodes in the graph
* @param the type of edges in the graph
* @return a builder
*/
public static Builder builder() {
return new Builder<>();
}
/**
* The graph.
*/
private final Graph graph;
/**
* The cached clustering result.
*/
private MaxMaxClustering clustering;
/**
* Create an instance of the MaxMax algorithm.
*
* @param graph the graph
*/
public MaxMax(Graph graph) {
this.graph = requireWeighted(requireUndirected(graph));
if (!isSimple(graph)) {
throw new IllegalArgumentException("Graph must be simple");
}
}
@Override
public MaxMaxClustering getClustering() {
if (isNull(clustering)) {
clustering = new Implementation<>(graph).compute();
}
return clustering;
}
/**
* Actual implementation of MaxMax.
*
* @param the type of nodes in the graph
* @param the type of edges in the graph
*/
protected static class Implementation {
/**
* The graph.
*/
protected final Graph graph;
/**
* The weights.
*/
protected final Map weights;
/**
* The directed graph.
*/
protected final Graph digraph;
/**
* Create an instance of the MaxMax clustering algorithm implementation.
*
* @param graph the graph
*/
public Implementation(Graph graph) {
this.graph = graph;
this.weights = new HashMap<>(graph.vertexSet().size());
this.digraph = SimpleDirectedGraph.createBuilder(DefaultEdge.class).build();
}
/**
* Perform clustering with MaxMax.
*
* @return the clustering
*/
public MaxMaxClustering compute() {
computeWeights();
buildArcs();
final var clusters = extractClusters();
final long elements = clusters.values().stream().flatMap(Collection::stream).distinct().count();
if (elements != graph.vertexSet().size()) {
throw new IllegalStateException("Clusters do not cover the nodes: " + elements + " vs. " + graph.vertexSet().size());
}
return new MaxMaxClustering.MaxMaxClusteringImpl<>(List.copyOf(clusters.values()),
new AsUnmodifiableGraph<>(digraph),
Collections.unmodifiableSet(clusters.keySet()));
}
/**
* Compute the weights for maximal affinity nodes.
*/
private void computeWeights() {
for (final var edge : graph.edgeSet()) {
final var u = graph.getEdgeSource(edge);
final var v = graph.getEdgeTarget(edge);
final var weight = graph.getEdgeWeight(edge);
if ((!weights.containsKey(u)) || (weights.get(u) < weight)) weights.put(u, weight);
if ((!weights.containsKey(v)) || (weights.get(v) < weight)) weights.put(v, weight);
}
if (!weights.keySet().equals(graph.vertexSet())) {
throw new IllegalArgumentException("Graph must not have zero-degree nodes");
}
}
/**
* Build the intermediate directed graph.
*/
protected void buildArcs() {
for (final var edge : graph.edgeSet()) {
final var u = graph.getEdgeSource(edge);
final var v = graph.getEdgeTarget(edge);
final var weight = graph.getEdgeWeight(edge);
if (weight == weights.get(u)) Graphs.addEdgeWithVertices(digraph, v, u);
if (weight == weights.get(v)) Graphs.addEdgeWithVertices(digraph, u, v);
}
}
/**
* Extract the quasi-strongly connected subgraphs from the intermediate directed graph.
*
* @return the map of cluster roots to the clusters
*/
protected Map> extractClusters() {
final var leaves = new HashSet<>(graph.vertexSet().size());
final var clusters = new HashMap>();
for (final var u : digraph.vertexSet()) {
if (!leaves.contains(u)) {
final var cluster = new HashSet();
cluster.add(u);
final var queue = new ArrayDeque<>(Graphs.successorListOf(digraph, u));
final var visited = new HashSet();
while (!queue.isEmpty()) {
final var v = queue.remove();
leaves.add(v);
if (!visited.contains(v)) {
clusters.remove(v);
if (digraph.containsVertex(v)) queue.addAll(Graphs.successorListOf(digraph, v));
cluster.add(v);
visited.add(v);
}
}
clusters.put(u, cluster);
}
}
return clusters;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy