docs: public OutlierResult run(Database database, Relation<O> relation) {
      StepProgress stepprog = LOG.isVerbose() ? new StepProgress("LDF", 3) : null;
      final int dim = RelationUtil.dimensionality(relation);
      DBIDs ids = relation.getDBIDs();
  
      LOG.beginStep(stepprog, 1, "Materializing neighborhoods w.r.t. distance function.");
      KNNQuery<O> knnq = DatabaseUtil.precomputedKNNQuery(database, relation, getDistanceFunction(), k);
  
      // Compute LDEs
      LOG.beginStep(stepprog, 2, "Computing LDEs.");
      WritableDoubleDataStore ldes = DataStoreUtil.makeDoubleStorage(ids, DataStoreFactory.HINT_HOT | DataStoreFactory.HINT_TEMP);
      FiniteProgress densProgress = LOG.isVerbose() ? new FiniteProgress("Densities", ids.size(), LOG) : null;
      for(DBIDIter it = ids.iter(); it.valid(); it.advance()) {
        final KNNList neighbors = knnq.getKNNForDBID(it, k);
        double sum = 0.0;
        int count = 0;
        // Fast version for double distances
        for(DoubleDBIDListIter neighbor = neighbors.iter(); neighbor.valid(); neighbor.advance()) {
          if(DBIDUtil.equal(neighbor, it)) {
            continue;
          }
          final double nkdist = knnq.getKNNForDBID(neighbor, k).getKNNDistance();
          if(!(nkdist > 0.) || nkdist == Double.POSITIVE_INFINITY) {
            sum = Double.POSITIVE_INFINITY;
            count++;
            break;
          }
          final double v = MathUtil.max(nkdist, neighbor.doubleValue()) / (h * nkdist);
          sum += kernel.density(v) / MathUtil.powi(h * nkdist, dim);
          count++;
        }
        ldes.putDouble(it, sum / count);
        LOG.incrementProcessed(densProgress);
      }
      LOG.ensureCompleted(densProgress);
  
      // Compute local density factors.
      LOG.beginStep(stepprog, 3, "Computing LDFs.");
      WritableDoubleDataStore ldfs = DataStoreUtil.makeDoubleStorage(ids, DataStoreFactory.HINT_STATIC);
      // track the maximum value for normalization.
      DoubleMinMax lofminmax = new DoubleMinMax();
  
      FiniteProgress progressLOFs = LOG.isVerbose() ? new FiniteProgress("Local Density Factors", ids.size(), LOG) : null;
      for(DBIDIter it = ids.iter(); it.valid(); it.advance()) {
        final double lrdp = ldes.doubleValue(it);
        final KNNList neighbors = knnq.getKNNForDBID(it, k);
        double sum = 0.0;
        int count = 0;
        for(DBIDIter neighbor = neighbors.iter(); neighbor.valid(); neighbor.advance()) {
          // skip the point itself
          if(DBIDUtil.equal(neighbor, it)) {
            continue;
          }
          sum += ldes.doubleValue(neighbor);
          count++;
        }
        sum /= count;
        final double div = lrdp + c * sum;
        double ldf = div == Double.POSITIVE_INFINITY ? (sum < Double.POSITIVE_INFINITY ? 0. : 1) : (div > 0) ? sum / div : 0;
        ldfs.putDouble(it, ldf);
        // update minimum and maximum
        lofminmax.put(ldf);
  
        LOG.incrementProcessed(progressLOFs);
      }
      LOG.ensureCompleted(progressLOFs);
  
      LOG.setCompleted(stepprog);
  
      // Build result representation.
      DoubleRelation scoreResult = new MaterializedDoubleRelation("Local Density Factor", "ldf-outlier", ldfs, ids);
      OutlierScoreMeta scoreMeta = new BasicOutlierScoreMeta(lofminmax.getMin(), lofminmax.getMax(), 0.0, 1. / c, 1 / (1 + c));
      OutlierResult result = new OutlierResult(scoreMeta, scoreResult);
  
      return result;
    }