/*
* 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.LinkedList;
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;
/**
* Cluster a given graph with the following algorithm:
* 1. Create one partition for each node.
* 2. Repeat until only k partitions are left:
* Merge the two partitions with the shorted distance
* between their barycenters.
*/
public class ClustererMinDist extends ClustererBase {
/** Partition the graph in n clusters in a run. */
protected final int numberOfClusters;
/**
*
* @param graph
* @param numberOfClusters
*/
public ClustererMinDist(GraphData graph, int numberOfClusters) {
super(graph);
this.numberOfClusters = Math.max(1, numberOfClusters);
}
protected static class ClusterPair implements Comparable<ClusterPair> {
protected final Group clusterA;
protected final Group clusterB;
protected final Double distanceOnPairing;
protected final RadiusOfGroup initialRadiusA;
protected final RadiusOfGroup initialRadiusB;
/**
* @param clusterA First cluster.
* @param clusterB Second cluster.
*/
public ClusterPair(Group clusterA, Group clusterB,
RadiusOfGroup initialRadiusA, RadiusOfGroup initialRadiusB) {
this.clusterA = clusterA;
this.clusterB = clusterB;
this.initialRadiusA = initialRadiusA;
this.initialRadiusB = initialRadiusB;
if (initialRadiusA != null) {
distanceOnPairing = calculateEucDistanceBetweenInitialBarycenters();
} else {
distanceOnPairing = calculateEucDistanceBetweenActualBarycenters();
}
}
/**
* Calculate the distance between the barycenters of two clusters in the pair.
* @return distance.
*/
protected double calculateEucDistanceBetweenActualBarycenters() {
float deltaX = clusterA.getX() - clusterB.getX();
float deltaY = clusterA.getY() - clusterB.getY();
float deltaZ = clusterA.getZ() - clusterB.getZ();
return Math.sqrt(
(deltaX * deltaX)
+ (deltaY * deltaY)
+ (deltaZ * deltaZ));
}
/**
* Calculate the distance between the barycenters of two clusters in the pair.
* @return distance.
*/
protected double calculateEucDistanceBetweenInitialBarycenters() {
float deltaX = initialRadiusA.getX() - initialRadiusB.getX();
float deltaY = initialRadiusA.getY() - initialRadiusB.getY();
float deltaZ = initialRadiusA.getZ() - initialRadiusB.getZ();
return Math.sqrt(
(deltaX * deltaX)
+ (deltaY * deltaY)
+ (deltaZ * deltaZ));
}
/**
* Get the distance of the pair of clusters.
* The distance does not get recalculated after changes of assigned nodes!
* @return distance.
*/
public double getEucDistanceBetweenBarycenters() {
return distanceOnPairing;
}
/**
* Compare two pairs of clusters by their distance.
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(ClusterPair compareWith) {
double compareWithDistance = compareWith.getEucDistanceBetweenBarycenters();
double thisDistance = getEucDistanceBetweenBarycenters();
if (compareWithDistance > thisDistance) {
return -1;
} else if (compareWithDistance < thisDistance) {
return 1;
} else {
return 0;
}
}
}
/**
* Merge two clusters.
* The elements of the first cluster (source) are moved to the second (target).
* @param source
* @param target
*/
protected void mergeClusters(Group source, Group target) {
List<GraphVertex> sourceNodes = source.getNodes();
for (int nodeIndex = sourceNodes.size()-1; nodeIndex > -1; nodeIndex--) {
GraphVertex vertex = sourceNodes.get(nodeIndex);
target.addNode(vertex);
source.removeNode(vertex, false);
}
}
@Override
protected List<Group> internalCreateClustersOfLayout()
throws InterruptedException {
List<Group> clusters = new LinkedList<Group>();
List<GraphVertex> graphVertices = graphData.getVertices();
// First step: Create one cluster for each vertex.
for (GraphVertex graphVertex : graphVertices) {
Group cluster = new Group("Cluster " + clusters.size(), graphData);
cluster.addNode(graphVertex);
cluster.setKind(GroupKind.CLUSTER);
clusters.add(cluster);
}
// Aggregate cluster until there are only k clusters left.
int initialNumOfClusters = clusters.size();
while (clusters.size() > numberOfClusters) {
System.out.println("Num of non-empty clusters: " + clusters.size());
setProgress(initialNumOfClusters - clusters.size(), initialNumOfClusters, "Running clustering.");
if (Thread.interrupted()) {
throw new InterruptedException();
}
// Determine pair of clusters with the shortest
// distance.
ClusterPair nearestPair = null;
int nearestPairIndexA = -1;
for (int i = 0; i < clusters.size(); i++) {
Group clusterA = (Group) clusters.get(i);
for (int j = i+1; j < clusters.size(); j++) {
Group clusterB = (Group) clusters.get(j);
if (clusterA != clusterB) {
if ((clusterA.getNodes().size() > 0)
&& (clusterB.getNodes().size() > 0)) {
ClusterPair pair = new ClusterPair(clusterA, clusterB, null, null);
double lPairDistance = pair.getEucDistanceBetweenBarycenters();
if ((nearestPair == null)
|| (nearestPair.getEucDistanceBetweenBarycenters() > lPairDistance)) {
nearestPair = pair;
nearestPairIndexA = i;
}
}
}
}
}
if (nearestPair != null) {
// Merge the pair of clusters with the smallest distance.
mergeClusters(nearestPair.clusterA, nearestPair.clusterB);
clusters.remove(nearestPairIndexA);
}
}
return clusters;
}
}