docs: public Clustering<Model> run(Database database, Relation<O> relation) {
      StepProgress stepprog = LOG.isVerbose() ? new StepProgress("LSDBC", 3) : null;
      final int dim = RelationUtil.dimensionality(relation);
      final double factor = FastMath.pow(2., alpha / dim);
  
      final DBIDs ids = relation.getDBIDs();
      LOG.beginStep(stepprog, 1, "Materializing kNN neighborhoods");
      KNNQuery<O> knnq = DatabaseUtil.precomputedKNNQuery(database, relation, getDistanceFunction(), k);
  
      LOG.beginStep(stepprog, 2, "Sorting by density");
      WritableDoubleDataStore dens = DataStoreUtil.makeDoubleStorage(ids, DataStoreFactory.HINT_HOT | DataStoreFactory.HINT_TEMP);
      fillDensities(knnq, ids, dens);
      ArrayModifiableDBIDs sids = DBIDUtil.newArray(ids);
      sids.sort(new DataStoreUtil.AscendingByDoubleDataStore(dens));
  
      LOG.beginStep(stepprog, 3, "Computing clusters");
      // Setup progress logging
      final FiniteProgress progress = LOG.isVerbose() ? new FiniteProgress("LSDBC Clustering", ids.size(), LOG) : null;
      final IndefiniteProgress clusprogress = LOG.isVerbose() ? new IndefiniteProgress("Number of clusters found", LOG) : null;
      // (Temporary) store the cluster ID assigned.
      final WritableIntegerDataStore clusterids = DataStoreUtil.makeIntegerStorage(ids, DataStoreFactory.HINT_TEMP, UNPROCESSED);
      // Note: these are not exact, as objects may be stolen from noise.
      final IntArrayList clustersizes = new IntArrayList();
      clustersizes.add(0); // Unprocessed dummy value.
      clustersizes.add(0); // Noise counter.
  
      // Implementation Note: using Integer objects should result in
      // reduced memory use in the HashMap!
      int clusterid = NOISE + 1;
      // Iterate over all objects in the database.
      for(DBIDIter id = sids.iter(); id.valid(); id.advance()) {
        // Skip already processed ids.
        if(clusterids.intValue(id) != UNPROCESSED) {
          continue;
        }
        // Evaluate Neighborhood predicate
        final KNNList neighbors = knnq.getKNNForDBID(id, k);
        // Evaluate Core-Point predicate:
        if(isLocalMaximum(neighbors.getKNNDistance(), neighbors, dens)) {
          double mindens = factor * neighbors.getKNNDistance();
          clusterids.putInt(id, clusterid);
          clustersizes.add(expandCluster(clusterid, clusterids, knnq, neighbors, mindens, progress));
          // start next cluster on next iteration.
          ++clusterid;
          if(clusprogress != null) {
            clusprogress.setProcessed(clusterid, LOG);
          }
        }
        else {
          // otherwise, it's a noise point
          clusterids.putInt(id, NOISE);
          clustersizes.set(NOISE, clustersizes.getInt(NOISE) + 1);
        }
        // We've completed this element
        LOG.incrementProcessed(progress);
      }
      // Finish progress logging.
      LOG.ensureCompleted(progress);
      LOG.setCompleted(clusprogress);
  
      LOG.setCompleted(stepprog);
  
      // Transform cluster ID mapping into a clustering result:
      ArrayList<ArrayModifiableDBIDs> clusterlists = new ArrayList<>(clusterid);
      // add storage containers for clusters
      for(int i = 0; i < clustersizes.size(); i++) {
        clusterlists.add(DBIDUtil.newArray(clustersizes.getInt(i)));
      }
      // do the actual inversion
      for(DBIDIter id = ids.iter(); id.valid(); id.advance()) {
        // Negative values are non-core points:
        int cid = clusterids.intValue(id);
        int cluster = Math.abs(cid);
        clusterlists.get(cluster).add(id);
      }
      clusterids.destroy();
  
      Clustering<Model> result = new Clustering<>("LSDBC", "lsdbc-clustering");
      for(int cid = NOISE; cid < clusterlists.size(); cid++) {
        boolean isNoise = (cid == NOISE);
        Cluster<Model> c;
        c = new Cluster<Model>(clusterlists.get(cid), isNoise, ClusterModel.CLUSTER);
        result.addToplevelCluster(c);
      }
      return result;
    }