/* * CCVisu is a tool for visual graph clustering * and general force-directed graph layout. * This file is part of CCVisu. * * Copyright (C) 2005-2012 Dirk Beyer * * CCVisu is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * CCVisu is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CCVisu; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Please find the GNU Lesser General Public License in file * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt * * Dirk Beyer (firstname.lastname@uni-passau.de) * University of Passau, Bavaria, Germany */ package org.sosy_lab.ccvisu.clustering; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.sosy_lab.ccvisu.graph.GraphData; import org.sosy_lab.ccvisu.graph.GraphVertex; import org.sosy_lab.ccvisu.graph.Group; import org.sosy_lab.ccvisu.graph.Group.GroupKind; import org.sosy_lab.ccvisu.graph.RadiusOfGroup; import org.sosy_lab.util.Stopwatch; import com.google.common.collect.MinMaxPriorityQueue; /** * Implementation of a clustering algorithm based on * @see org.sosy-lab.ccvisu.clustering.ClustererMinDist * with some heuristics that enhance performance. */ public class ClustererMinDistPerc extends ClustererMinDist { /** * Put all nodes with a distance of at most n percent * of the diagonal of the layout in one cluster. * Heuristic that helps to minimize execution time of the algorithm. * A good value is 0.05 */ private final double maxDistancePercentToAutoMerge; /** * Stop creating clusters of the graph after all clusters * have a certain distance (percent of the diagonal of the layout). */ private final double minClusterDistancePercent; /** * Constructor. * * @param graph Graph that should be clustered. * @param numberOfClusters Number of partitions hat should be found at least. * @param maxDistancePercentToAutoMerge @see ClustererMinDistPerc#maxDistancePercentToAutoMerge * @param minClusterDistancePercent @see ClustererMinDistPerc#minClusterDistancePercent */ public ClustererMinDistPerc(GraphData graph, int numberOfClusters, double maxDistancePercentToAutoMerge, double minClusterDistancePercent) { super(graph, numberOfClusters); this.maxDistancePercentToAutoMerge = maxDistancePercentToAutoMerge; this.minClusterDistancePercent = minClusterDistancePercent; } @Override protected List<Group> internalCreateClustersOfLayout() throws InterruptedException { Stopwatch stopwatch = Stopwatch.createAndStart(); List<Group> clusters = new ArrayList<Group>(); List<GraphVertex> vertices = graphData.getVertices(); // // // Initially put each node in a separate cluster. // setProgress(0, vertices.size(), "Creating initial clusters."); double minX = Double.MAX_VALUE; double minY = Double.MAX_VALUE; double minZ = Double.MAX_VALUE; double maxX = Double.MIN_VALUE; double maxY = Double.MIN_VALUE; double maxZ = Double.MIN_VALUE; int clusterSeqNo = 0; for (GraphVertex vertex : vertices) { Group vertexCluster = new Group("Cluster " + clusterSeqNo++, graphData); vertexCluster.setKind(GroupKind.CLUSTER); vertexCluster.addNode(vertex); clusters.add(vertexCluster); maxX = Math.max(maxX, vertex.getPosition().x); maxY = Math.max(maxY, vertex.getPosition().y); maxZ = Math.max(maxZ, vertex.getPosition().z); minX = Math.min(minX, vertex.getPosition().x); minY = Math.min(minY, vertex.getPosition().y); minZ = Math.min(minZ, vertex.getPosition().z); } // // // Calculate the diagonal of the layout. // double layoutDistanceX = Math.abs(maxX - minX); double layoutDistanceY = Math.abs(maxY - minY); double layoutDistanceZ = Math.abs(maxZ - minZ); double layoutDiagonal = Math.sqrt( layoutDistanceX * layoutDistanceX + layoutDistanceZ * layoutDistanceZ + layoutDistanceY * layoutDistanceY); // // // Calculate the parameters. // int initialNumOfClusters = clusters.size(); int numberOfClustersWithNodes = initialNumOfClusters; double maxDistanceToAutoMerge = layoutDiagonal * maxDistancePercentToAutoMerge; double minClusterDistanceAbsoulte = layoutDiagonal * minClusterDistancePercent; // // // Aggregate cluster until there are only k clusters left. // int iterationNumber = 0; int mergesInIteration = 0; do { iterationNumber++; mergesInIteration = 0; HashMap<Group,RadiusOfGroup> fixedBarycenters = new HashMap<Group, RadiusOfGroup>(); setProgress(initialNumOfClusters - numberOfClustersWithNodes, initialNumOfClusters, "Creating clusters"); System.out.println("Num of non-empty clusters: " + numberOfClustersWithNodes); // Calculate the distance between all clusters. // Merge clusters if their distance is less than lMaxDistanceToAutoMerge. MinMaxPriorityQueue<ClusterPair> nearestPairs = MinMaxPriorityQueue.maximumSize(100).create(); int highestClusterWithRadius = -1; for (int a = clusters.size() - 1; a >= 0; a--) { Group clusterA = clusters.get(a); if (clusterA.getNodes().size() > 0) { RadiusOfGroup barycenterA = null; if (a > highestClusterWithRadius) { fixedBarycenters.put(clusterA, new RadiusOfGroup(clusterA.getNodes())); highestClusterWithRadius = a; } else { barycenterA = fixedBarycenters.get(clusterA); } if (Thread.interrupted()) { throw new InterruptedException(); } for (int b = a - 1; b >= 0; b--) { Group clusterB = clusters.get(b); if (clusterB.getNodes().size() > 0) { RadiusOfGroup barycenterB = null; if (b > highestClusterWithRadius) { fixedBarycenters.put(clusterB, new RadiusOfGroup(clusterB.getNodes())); highestClusterWithRadius = b; } else { barycenterB = fixedBarycenters.get(clusterB); } ClusterPair clusterPair = new ClusterPair(clusterA, clusterB, barycenterA, barycenterB); double pairDistance = clusterPair.getEucDistanceBetweenBarycenters(); // First stage merging: // Merge clusters without recalculating the distances to the // merged clusters. // * Only merge clusters having a distance less than... if (pairDistance <= minClusterDistanceAbsoulte) { if (pairDistance < maxDistanceToAutoMerge) { if (numberOfClustersWithNodes > numberOfClusters) { mergeClusters(clusterB, clusterA); mergesInIteration++; numberOfClustersWithNodes--; } } else { nearestPairs.add(clusterPair); } } } } } } int mergesIndSecondPhase = 0; double nearestPairDistance = -1; do { if (numberOfClustersWithNodes > numberOfClusters) { ClusterPair pair = nearestPairs.poll(); if (pair != null) { double pairDistance = pair.getEucDistanceBetweenBarycenters(); if (nearestPairDistance == -1) { nearestPairDistance = pairDistance; } if (mergesIndSecondPhase == 0 || ((pairDistance / nearestPairDistance) - 1 <= 0.01)) { Group sourceGroup = pair.clusterA; Group targetGroup = pair.clusterB; if (targetGroup.getNodes().size() == 0) { sourceGroup = pair.clusterB; targetGroup = pair.clusterA; } if (sourceGroup.getNodes().size() > 0 && targetGroup.getNodes().size() > 0) { if (numberOfClustersWithNodes > numberOfClusters) { mergeClusters(sourceGroup, targetGroup); numberOfClustersWithNodes--; mergesInIteration++; mergesIndSecondPhase++; } } } else { break; } } else { break; } } else { break; } } while (true); // More merging of clusters necessary? System.out.println(String.format("%d merges in iteration %d", mergesInIteration, iterationNumber)); } while (mergesInIteration > 0); for (int i = clusters.size()-1; i > 0; i--) { Group group = clusters.get(i); if (group.getNodes().size() == 0) { clusters.remove(i); } else { System.out.println(String.format("%s with %d nodes.", group.getName(), group.getNodes().size())); } } setProgress(1, 1, stopwatch.stop().toString()); return clusters; } }