// visualization
List<SteepArea> salist = new ArrayList<SteepArea>();
List<SteepDownArea> sdaset = new java.util.Vector<SteepDownArea>();
ModifiableHierarchy<Cluster<OPTICSModel>> hier = new HierarchyHashmapList<Cluster<OPTICSModel>>();
HashSet<Cluster<OPTICSModel>> curclusters = new HashSet<Cluster<OPTICSModel>>();
HashSetModifiableDBIDs unclaimedids = DBIDUtil.newHashSet(relation.getDBIDs());
SteepScanPosition<N> scan = new SteepScanPosition<N>(clusterOrder);
while(scan.hasNext()) {
final int curpos = scan.index;
// Update maximum-inbetween
mib = Math.max(mib, scan.ecurr.getReachability().doubleValue());
// The last point cannot be the start of a steep area.
if(scan.esucc != null) {
// Xi-steep down area
if(scan.steepDown(ixi)) {
// Update mib values with current mib and filter
updateFilterSDASet(mib, sdaset, ixi);
final double startval = scan.ecurr.getReachability().doubleValue();
int startsteep = scan.index;
int endsteep = Math.min(scan.index + 1, clusterOrder.size());
{
while(scan.hasNext()) {
scan.next();
// not going downward at all - stop here.
if(!scan.steepDown(1.0)) {
break;
}
// still steep - continue.
if(scan.steepDown(ixi)) {
endsteep = Math.min(scan.index + 1, clusterOrder.size());
}
else {
// Stop looking after minpts "flat" steps.
if(scan.index - endsteep > minpts) {
break;
}
}
}
}
mib = clusterOrder.get(endsteep).getReachability().doubleValue();
final SteepDownArea sda = new SteepDownArea(startsteep, endsteep, startval, 0);
if(logger.isDebuggingFinest()) {
logger.debugFinest("Xi " + sda.toString());
}
sdaset.add(sda);
if(salist != null) {
salist.add(sda);
}
continue;
}
else
// Xi-steep up area
if(scan.steepUp(ixi)) {
// Update mib values with current mib and filter
updateFilterSDASet(mib, sdaset, ixi);
final SteepUpArea sua;
// Compute steep-up area
{
int startsteep = scan.index;
int endsteep = scan.index + 1;
mib = scan.ecurr.getReachability().doubleValue();
double esuccr = scan.esucc.getReachability().doubleValue();
// There is nothing higher than infinity
if(!Double.isInfinite(esuccr)) {
// find end of steep-up-area, eventually updating mib again
while(scan.hasNext()) {
scan.next();
// not going upward - stop here.
if(!scan.steepUp(1.0)) {
break;
}
// still steep - continue.
if(scan.steepUp(ixi)) {
endsteep = Math.min(scan.index + 1, clusterOrder.size() - 1);
mib = scan.ecurr.getReachability().doubleValue();
esuccr = scan.esucc.getReachability().doubleValue();
}
else {
// Stop looking after minpts non-up steps.
if(scan.index - endsteep > minpts) {
break;
}
}
}
}
sua = new SteepUpArea(startsteep, endsteep, esuccr);
if(logger.isDebuggingFinest()) {
logger.debugFinest("Xi " + sua.toString());
}
if(salist != null) {
salist.add(sua);
}
}
// Validate and computer clusters
// logger.debug("SDA size:"+sdaset.size()+" "+sdaset);
ListIterator<SteepDownArea> sdaiter = sdaset.listIterator(sdaset.size());
// Iterate backwards for correct hierarchy generation.
while(sdaiter.hasPrevious()) {
SteepDownArea sda = sdaiter.previous();
// logger.debug("Comparing: eU="+mib.doubleValue()+" SDA: "+sda.toString());
// Condition 3b: end-of-steep-up > maximum-in-between lower
if(mib * ixi < sda.getMib()) {
continue;
}
// By default, clusters cover both the steep up and steep down area
int cstart = sda.getStartIndex();
int cend = sua.getEndIndex();
// However, we sometimes have to adjust this (Condition 4):
{
// Case b)
if(sda.getMaximum() * ixi >= sua.getMaximum()) {
while(cstart < sda.getEndIndex()) {
if(clusterOrder.get(cstart + 1).getReachability().doubleValue() > sua.getMaximum()) {
cstart++;
}
else {
break;
}
}
}
// Case c)
else if(sua.getMaximum() * ixi >= sda.getMaximum()) {
while(cend > sua.getStartIndex()) {
if(clusterOrder.get(cend - 1).getReachability().doubleValue() > sda.getMaximum()) {
cend--;
}
else {
break;
}
}
}
// Case a) is the default
}
// Condition 3a: obey minpts
if(cend - cstart + 1 < minpts) {
continue;
}
// Build the cluster
ModifiableDBIDs dbids = DBIDUtil.newArray();
for(int idx = cstart; idx <= cend; idx++) {
final DBID dbid = clusterOrder.get(idx).getID();
// Collect only unclaimed IDs.
if(unclaimedids.remove(dbid)) {
dbids.add(dbid);
}
}
if(logger.isDebuggingFine()) {
logger.debugFine("Found cluster with " + dbids.size() + " new objects, length " + (cstart - cend + 1));
}
OPTICSModel model = new OPTICSModel(cstart, cend);
Cluster<OPTICSModel> cluster = new Cluster<OPTICSModel>("Cluster_" + cstart + "_" + cend, dbids, model, hier);
// Build the hierarchy
{
Iterator<Cluster<OPTICSModel>> iter = curclusters.iterator();
while(iter.hasNext()) {
Cluster<OPTICSModel> clus = iter.next();
OPTICSModel omodel = clus.getModel();
if(model.getStartIndex() <= omodel.getStartIndex() && omodel.getEndIndex() <= model.getEndIndex()) {
hier.add(cluster, clus);
iter.remove();
}
}
}
curclusters.add(cluster);
}
}
}
// Make sure to advance at least one step
if(curpos == scan.index) {
scan.next();
}
}
if(curclusters.size() > 0 || unclaimedids.size() > 0) {
final Clustering<OPTICSModel> clustering = new Clustering<OPTICSModel>("OPTICS Xi-Clusters", "optics");
if(unclaimedids.size() > 0) {
final Cluster<OPTICSModel> allcluster;
if(clusterOrder.get(clusterOrder.size() - 1).getReachability().isInfiniteDistance()) {
allcluster = new Cluster<OPTICSModel>("Noise", unclaimedids, true, new OPTICSModel(0, clusterOrder.size() - 1), hier);
}
else {