docs: public FrequentItemsetsResult run(Relation<BitVector> relation) {
      DBIDs ids = relation.getDBIDs();
      List<Itemset> solution = new ArrayList<>();
      final int size = ids.size();
      final int needed = getMinimumSupport(size);
  
      // TODO: we don't strictly require a vector field.
      // We could work with knowing just the maximum dimensionality beforehand.
      VectorFieldTypeInformation<BitVector> meta = RelationUtil.assumeVectorField(relation);
      if(size > 0) {
        final int dim = meta.getDimensionality();
        Duration timeone = LOG.newDuration(STAT + "1-items.time").begin();
        List<OneItemset> oneitems = buildFrequentOneItemsets(relation, dim, needed);
        LOG.statistics(timeone.end());
        if(LOG.isStatistics()) {
          LOG.statistics(new LongStatistic(STAT + "1-items.frequent", oneitems.size()));
          LOG.statistics(new LongStatistic(STAT + "1-items.transactions", ids.size()));
        }
        if(LOG.isDebuggingFine()) {
          LOG.debugFine(debugDumpCandidates(new StringBuilder(), oneitems, meta));
        }
        if(minlength <= 1) {
          solution.addAll(oneitems);
        }
        if(oneitems.size() >= 2 && maxlength >= 2) {
          Duration timetwo = LOG.newDuration(STAT + "2-items.time").begin();
          ArrayModifiableDBIDs survivors = DBIDUtil.newArray(ids.size());
          List<? extends Itemset> candidates = buildFrequentTwoItemsets(oneitems, relation, dim, needed, ids, survivors);
          ids = survivors; // Continue with reduced set of transactions.
          LOG.statistics(timetwo.end());
          if(LOG.isStatistics()) {
            LOG.statistics(new LongStatistic(STAT + "2-items.frequent", candidates.size()));
            LOG.statistics(new LongStatistic(STAT + "2-items.transactions", ids.size()));
          }
          if(LOG.isDebuggingFine()) {
            LOG.debugFine(debugDumpCandidates(new StringBuilder(), candidates, meta));
          }
          if(minlength <= 2) {
            solution.addAll(candidates);
          }
          for(int length = 3; length <= maxlength && candidates.size() >= length; length++) {
            Duration timel = LOG.newDuration(STAT + length + "-items.time").begin();
            // Join to get the new candidates
            candidates = aprioriGenerate(candidates, length, dim);
            if(LOG.isDebuggingFinest()) {
              LOG.debugFinest(debugDumpCandidates(new StringBuilder().append("Before pruning: "), candidates, meta));
            }
            survivors = DBIDUtil.newArray(ids.size());
            candidates = frequentItemsets(candidates, relation, needed, ids, survivors, length);
            ids = survivors; // Continue with reduced set of transactions.
            LOG.statistics(timel.end());
            if(LOG.isStatistics()) {
              LOG.statistics(new LongStatistic(STAT + length + "-items.frequent", candidates.size()));
              LOG.statistics(new LongStatistic(STAT + length + "-items.transactions", ids.size()));
            }
            if(LOG.isDebuggingFine()) {
              LOG.debugFine(debugDumpCandidates(new StringBuilder(), candidates, meta));
            }
            solution.addAll(candidates);
          }
        }
      }
      return new FrequentItemsetsResult("APRIORI", "apriori", solution, meta, size);
    }