docs: public OutlierResult run(Database database, Relation<N> spatial, Relation<O> relation) {
      final NeighborSetPredicate npred = getNeighborSetPredicateFactory().instantiate(database, spatial);
      DistanceQuery<O> distFunc = getNonSpatialDistanceFunction().instantiate(relation);
  
      WritableDoubleDataStore lrds = DataStoreUtil.makeDoubleStorage(relation.getDBIDs(), DataStoreFactory.HINT_TEMP | DataStoreFactory.HINT_HOT);
      WritableDoubleDataStore lofs = DataStoreUtil.makeDoubleStorage(relation.getDBIDs(), DataStoreFactory.HINT_STATIC);
      DoubleMinMax lofminmax = new DoubleMinMax();
  
      // Compute densities
      for(DBIDIter iditer = relation.iterDBIDs(); iditer.valid(); iditer.advance()) {
        DBIDs neighbors = npred.getNeighborDBIDs(iditer);
        double avg = 0;
        for(DBIDIter iter = neighbors.iter(); iter.valid(); iter.advance()) {
          avg += distFunc.distance(iditer, iter);
        }
        double lrd = 1 / (avg / neighbors.size());
        if(Double.isNaN(lrd)) {
          lrd = 0;
        }
        lrds.putDouble(iditer, lrd);
      }
  
      // Compute density quotients
      for(DBIDIter iditer = relation.iterDBIDs(); iditer.valid(); iditer.advance()) {
        DBIDs neighbors = npred.getNeighborDBIDs(iditer);
        double avg = 0;
        for(DBIDIter iter = neighbors.iter(); iter.valid(); iter.advance()) {
          avg += lrds.doubleValue(iter);
        }
        final double lrd = (avg / neighbors.size()) / lrds.doubleValue(iditer);
        if(!Double.isNaN(lrd)) {
          lofs.putDouble(iditer, lrd);
          lofminmax.put(lrd);
        }
        else {
          lofs.putDouble(iditer, 0.0);
        }
      }
  
      // Build result representation.
      DoubleRelation scoreResult = new MaterializedDoubleRelation("Spatial Outlier Factor", "sof-outlier", lofs, relation.getDBIDs());
      OutlierScoreMeta scoreMeta = new QuotientOutlierScoreMeta(lofminmax.getMin(), lofminmax.getMax(), 0.0, Double.POSITIVE_INFINITY, 1.0);
      OutlierResult or = new OutlierResult(scoreMeta, scoreResult);
      or.addChildResult(npred);
      return or;
    }