code: public static <S, I, O> Optional<ADSNode<S, I, O>> computeOptimal(final MealyMachine<S, I, ?, O> automaton,
                                                                        final Alphabet<I> input,
                                                                        final Set<S> states,
                                                                        final CostAggregator costAggregator) {
  
          if (states.size() == 1) {
              return ADS.compute(automaton, input, states);
          }
  
          final Map<Set<S>, Optional<SearchState<S, I, O>>> searchStateCache = new HashMap<>();
          final Set<Set<S>> traceCache = new HashSet<>();
  
          final Optional<SearchState<S, I, O>> searchState = exploreSearchSpace(automaton,
                                                                                input,
                                                                                states,
                                                                                costAggregator,
                                                                                searchStateCache,
                                                                                traceCache,
                                                                                Integer.MAX_VALUE);
  
          if (!searchState.isPresent()) {
              return Optional.empty();
          }
  
          final Map<S, S> initialMapping =
                  states.stream().collect(Collectors.toMap(Function.identity(), Function.identity()));
  
          return Optional.of(constructADS(automaton, initialMapping, searchState.get()));
      }
docs: Computes an ADS by iterating over the successor tree in a breadth-first manner, yielding an optimal (dependant on
  the passed optimization function) ADS.
  
  @param automaton
  The automaton for which an ADS should be computed
  @param input
  the input alphabet of the automaton
  @param states
  the set of states which should be distinguished by the computed ADS
  @param costAggregator
  the optimization function by which solutions should be pruned
  @param <S>
  (hypothesis) state type
  @param <I>
  input alphabet type
  @param <O>
  output alphabet type
  
  @return {@code Optional.empty()} if there exists no ADS that distinguishes the given states, a valid ADS
  otherwise.