docs: public void computeSetsBounds(Relation<V> points, int minSplitSize, DBIDs ptList) {
      this.minSplitSize = minSplitSize;
      final int size = points.size();
      final int dim = RelationUtil.dimensionality(points);
      this.points = points;
  
      // perform O(log N+log dim) splits of the entire point sets projections
      int nPointSetSplits = (int) (logOProjectionConst * MathUtil.log2(size * dim + 1));
      // perform O(log N+log dim) projections of the point set onto a random line
      int nProject1d = (int) (logOProjectionConst * MathUtil.log2(size * dim + 1));
  
      LOG.statistics(new LongStatistic(PREFIX + ".partition-size", nPointSetSplits));
      LOG.statistics(new LongStatistic(PREFIX + ".num-projections", nProject1d));
      splitsets = new ArrayList<>();
  
      // perform projections of points
      projectedPoints = new DoubleDataStore[nProject1d];
      DoubleDataStore[] tmpPro = new DoubleDataStore[nProject1d];
  
      Random rand = rnd.getSingleThreadedRandom();
      FiniteProgress projp = LOG.isVerbose() ? new FiniteProgress("Random projections", nProject1d, LOG) : null;
      for(int j = 0; j < nProject1d; j++) {
        double[] currRp = new double[dim];
        double sum = 0;
        for(int i = 0; i < dim; i++) {
          double fl = rand.nextDouble() - 0.5;
          currRp[i] = fl;
          sum += fl * fl;
        }
        sum = FastMath.sqrt(sum);
        for(int i = 0; i < dim; i++) {
          currRp[i] /= sum;
        }
        WritableDoubleDataStore currPro = DataStoreUtil.makeDoubleStorage(ptList, DataStoreFactory.HINT_HOT);
  
        for(DBIDIter it = ptList.iter(); it.valid(); it.advance()) {
          NumberVector vecPt = points.get(it);
          // Dot product:
          double sum2 = 0;
          for(int i = 0; i < dim; i++) {
            sum2 += currRp[i] * vecPt.doubleValue(i);
          }
          currPro.put(it, sum2);
        }
        projectedPoints[j] = currPro;
        LOG.incrementProcessed(projp);
      }
      LOG.ensureCompleted(projp);
      // Log the number of scalar projections performed.
      long numprod = nProject1d * (long) ptList.size();
      LOG.statistics(new LongStatistic(PREFIX + ".num-scalar-products", numprod));
  
      // split entire point set, reuse projections by shuffling them
      IntArrayList proind = new IntArrayList(nProject1d);
      for(int j = 0; j < nProject1d; j++) {
        proind.add(j);
      }
      FiniteProgress splitp = LOG.isVerbose() ? new FiniteProgress("Splitting data", nPointSetSplits, LOG) : null;
      for(int avgP = 0; avgP < nPointSetSplits; avgP++) {
        // shuffle projections
        for(int i = 0; i < nProject1d; i++) {
          tmpPro[i] = projectedPoints[i];
        }
        // Shuffle axes (Fisher-Yates)
        for(int i = 1; i < nProject1d; i++) {
          final int j = rand.nextInt(i);
          proind.set(i, proind.set(j, proind.getInt(i))); // Swap i,j
        }
        IntIterator it = proind.iterator();
        int i = 0;
        while(it.hasNext()) {
          int cind = it.nextInt();
          projectedPoints[cind] = tmpPro[i];
          i++;
        }
  
        // split point set
        splitupNoSort(DBIDUtil.newArray(ptList), 0, size, 0, rand);
        LOG.incrementProcessed(splitp);
      }
      LOG.ensureCompleted(splitp);
    }