/*
* Copyright 2004-2010 Information & Software Engineering Group (188/1)
* Institute of Software Technology and Interactive Systems
* Vienna University of Technology, Austria
*
* 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.ifs.tuwien.ac.at/dm/somtoolbox/license.html
*
* 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 at.tuwien.ifs.somtoolbox.visualization.clustering;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import java.util.logging.Logger;
import at.tuwien.ifs.somtoolbox.apps.viewer.GeneralUnitPNode;
import at.tuwien.ifs.somtoolbox.layers.metrics.L2Metric;
import at.tuwien.ifs.somtoolbox.layers.metrics.MetricException;
/**
* Complete Linkage Clustering Algorithm. This class is not compatible with mnemonic SOMs (and probably also not
* compatible with hierarchical SOMs) The updating uf the distances can probabla be optimised (see
* {@link WardsLinkageTreeBuilderAll} - lazyUpdate)
*
* @author Angela Roiger
* @version $Id: CompleteLinkageTreeBuilder.java 3938 2010-11-17 15:15:25Z mayer $
*/
public class CompleteLinkageTreeBuilder extends TreeBuilder {
/**
* Calculation of the Clustering. This code is only compatible with rectangular, non hierarchical SOMs!
*
* @param units the GeneralUnitPNode Array containing all the units of the SOM
* @return the ClusteringTree (i.e. the top node of the tree)
*/
@Override
public ClusteringTree createTree(GeneralUnitPNode[][] units) throws ClusteringAbortedException {
Logger.getLogger("at.tuwien.ifs.somtoolbox").info("Start Clustering ");
this.level = units.length * units[0].length;
// initialize monitor
resetMonitor(2 * level);
TreeSet<NodeDistance> dists = calculateAllDistances(units);
NodeDistance tmpDist;
ClusterNode newNode = null;
while (dists.size() > 0) {
tmpDist = dists.first();
// Logger.getLogger("at.tuwien.ifs.somtoolbox").info("Shortest: "+tmpDist.dist);
dists.remove(tmpDist);
level--;
incrementMonitor();
allowAborting();
Logger.getLogger("at.tuwien.ifs.somtoolbox").info("Level: " + level);
newNode = new ClusterNode(tmpDist.n1, tmpDist.n2, level, tmpDist.dist);
HashMap<List<ClusterNode>, NodeDistance> duplicateEliminator = new HashMap<List<ClusterNode>, NodeDistance>();
List<ClusterNode> pair;
// remove not needed connections and change distances from n1,n2 to newNode
for (Iterator<NodeDistance> i = dists.iterator(); i.hasNext();) {
NodeDistance x = i.next();
if (x.n1 == tmpDist.n1 || x.n1 == tmpDist.n2) {
x.n1 = newNode;
}
if (x.n2 == tmpDist.n1 || x.n2 == tmpDist.n2) {
x.n2 = newNode;
}
if (x.n1 == x.n2) {
i.remove();
} else if (x.n1 == newNode || x.n2 == newNode) {
// & keep only the longest distance for each connection
if (x.n1 == newNode) { // make pair where new node is first
pair = Arrays.asList(new ClusterNode[] { x.n1, x.n2 });
} else {
pair = Arrays.asList(new ClusterNode[] { x.n2, x.n1 });
}
// replaces any existing entry with the same pair
duplicateEliminator.put(pair, x);
i.remove();
Logger.getLogger("at.tuwien.ifs.somtoolbox").finest("Removing @ " + level);
}
}
// keep only the longest distance for each connection ... code moved up
/*
* HashMap duplicateEliminator = new HashMap(); List pair; for (Iterator i = dists.iterator(); i.hasNext();) { NodeDistance x =
* (NodeDistance)i.next(); if ((x.n1==newNode)||(x.n2==newNode)){ pair = Arrays.asList(new Object[] {x.n1,x.n2}); // replaces any existing
* enty with the same pair duplicateEliminator.put(pair, x); i.remove(); } }
*/
// now dists is empty and duplicateEliminator only
// contains the longest distances between 2 clusters
dists.addAll(duplicateEliminator.values());
}
finishMonitor();
Logger.getLogger("at.tuwien.ifs.somtoolbox").info("Finished Clustering - Complete Linkage");
return new ClusteringTree(newNode, units.length);
}
/**
* Calculates all distances between all units.
*
* @param units A GeneralUnitPNode[][] containing the Units of the som
* @return TreeSet of NodeDistances containing the distances between the units starting with the smallest.
* @throws ClusteringAbortedException when the clustering was aborted.
*/
private TreeSet<NodeDistance> calculateAllDistances(GeneralUnitPNode[][] units) throws ClusteringAbortedException {
int xdim = units.length;
int ydim = units[0].length;
// Angela: Pfusch :)
L2Metric l1 = new L2Metric();
// int count=0;
ClusterNode[][] tmp = new ClusterNode[xdim][ydim];
TreeSet<NodeDistance> dists = new TreeSet<NodeDistance>();
// create all basic Nodes
for (int i = 0; i < xdim; i++) {
for (int j = 0; j < ydim; j++) {
tmp[i][j] = new ClusterNode(units[i][j], level);
}
}
// Angela: all distances:
for (int i = 0; i < xdim; i++) {
for (int j = 0; j < ydim; j++) {
incrementMonitor();
allowAborting();
try {
for (int k = 0; k < xdim; k++) {
for (int l = 0; l < ydim; l++) {
// if ((i!=k)&&(j!=l))
if (!(i == k && j == l)) {
dists.add(new NodeDistance(tmp[i][j], tmp[k][l], l1.distance(
units[i][j].getUnit().getWeightVector(),
units[k][l].getUnit().getWeightVector())));
}
}
}
} catch (MetricException e) {
Logger.getLogger("at.tuwien.ifs.somtoolbox").severe("Cannot create clustering: " + e.getMessage());
}
}
}
return dists;
}
@Override
public String getClusteringAlgName() {
return "CompleteLinkage";
}
}