* @throws de.lmu.ifi.dbs.elki.utilities.UnableToComplyException
*/
public Clustering<Model> run(Database database, Relation<NumberVector<?, ?>> relation) throws UnableToComplyException {
Clustering<Model> ret = new Clustering<Model>("LMCLUS Clustering", "lmclus-clustering");
FiniteProgress progress = logger.isVerbose() ? new FiniteProgress("Clustered objects", relation.size(), logger) : null;
IndefiniteProgress cprogress = logger.isVerbose() ? new IndefiniteProgress("Clusters found", logger) : null;
ModifiableDBIDs unclustered = DBIDUtil.newHashSet(relation.getDBIDs());
final int maxdim = Math.min(maxLMDim, DatabaseUtil.dimensionality(relation));
int cnum = 0;
while(unclustered.size() > minsize) {
DBIDs current = unclustered;
int lmDim = 1;
for(int k = 1; k <= maxdim; k++) {
// Implementation note: this while loop is from the original publication
// and the published LMCLUS source code. It doesn't make sense to me -
// it is lacking a stop criterion other than "cluster is too small" and
// "cluster is inseparable"! Additionally, there is good criterion for
// stopping at the appropriate dimensionality either.
while(true) {
Separation separation = findSeparation(relation, current, k);
// logger.verbose("k: " + k + " goodness: " + separation.goodness +
// " threshold: " + separation.threshold);
if(separation.goodness <= sensitivityThreshold) {
break;
}
ModifiableDBIDs subset = DBIDUtil.newArray(current.size());
for(DBID id : current) {
if(deviation(relation.get(id).getColumnVector().minusEquals(separation.originV), separation.basis) < separation.threshold) {
subset.add(id);
}
}
// logger.verbose("size:"+subset.size());
if(subset.size() < minsize) {
break;
}
current = subset;
lmDim = k;
// System.out.println("Partition: " + subset.size());
}
}
// No more clusters found
if(current.size() < minsize || current == unclustered) {
break;
}
// New cluster found
// TODO: annotate cluster with dimensionality
final Cluster<Model> cluster = new Cluster<Model>(current);
cluster.setName("Cluster_" + lmDim + "d_" + cnum);
cnum++;
ret.addCluster(cluster);
// Remove from main working set.
unclustered.removeDBIDs(current);
if(progress != null) {
progress.setProcessed(relation.size() - unclustered.size(), logger);
}
if(cprogress != null) {
cprogress.setProcessed(cnum, logger);
}
}
// Remaining objects are noise
if(unclustered.size() > 0) {
ret.addCluster(new Cluster<Model>(unclustered, true));
}
if(progress != null) {
progress.setProcessed(relation.size(), logger);
progress.ensureCompleted(logger);
}
if(cprogress != null) {
cprogress.setCompleted(logger);
}
return ret;
}