docs: protected void computeOutlierScores(KNNQuery<O> knnq, final DBIDs ids, WritableDataStore<double[]> densities, WritableDoubleDataStore kdeos, DoubleMinMax minmax) {
      final int knum = kmax + 1 - kmin;
      FiniteProgress prog = LOG.isVerbose() ? new FiniteProgress("Computing KDEOS scores", ids.size(), LOG) : null;
  
      double[][] scratch = new double[knum][kmax + 5];
      MeanVariance mv = new MeanVariance();
  
      for(DBIDIter iter = ids.iter(); iter.valid(); iter.advance()) {
        double[] dens = densities.get(iter);
        KNNList neighbors = knnq.getKNNForDBID(iter, kmax + 1);
        if(scratch[0].length < neighbors.size()) {
          // Resize scratch. Add some extra margin again.
          scratch = new double[knum][neighbors.size() + 5];
        }
        { // Store density matrix of neighbors
          int i = 0;
          for(DoubleDBIDListIter neighbor = neighbors.iter(); neighbor.valid(); neighbor.advance(), i++) {
            double[] ndens = densities.get(neighbor);
            for(int k = 0; k < knum; k++) {
              scratch[k][i] = ndens[k];
            }
          }
          assert (i == neighbors.size());
        }
        // Compute means and stddevs for each k
        double score = 0.;
        for(int i = 0; i < knum; i++) {
          mv.reset();
          for(int j = 0; j < neighbors.size(); j++) {
            mv.put(scratch[i][j]);
          }
          final double mean = mv.getMean(), stddev = mv.getSampleStddev();
          if(stddev > 0.) {
            score += (mean - dens[i]) / stddev;
          }
        }
        score /= knum; // average
        score = NormalDistribution.standardNormalCDF(score);
        minmax.put(score);
        kdeos.put(iter, score);
        LOG.incrementProcessed(prog);
      }
      LOG.ensureCompleted(prog);
    }