/**
   * Get the maximum value by which score difference between any two recommendations can be changed
   * by post processing.
   *
   * @param input input to the recommendation engine. Typically the person or item recommendations
   *     are being computed for.
   * @param context additional information about the recommendation process.
   * @return maximum relative change. If unknown, {@link Float#POSITIVE_INFINITY} will be returned.
   */
  private float maxRelativeChange(IN input, Context<OUT, IN> context) {
    float result = 0f;

    for (PostProcessor<OUT, IN> postProcessor : postProcessors) {
      float posInfluence = postProcessor.maxPositiveScore(input, context);
      float negInfluence = postProcessor.maxNegativeScore(input, context);

      if (posInfluence < 0) {
        throw new IllegalStateException(
            postProcessor
                + " has a negative influence score ("
                + posInfluence
                + "), should not be negative");
      }

      if (negInfluence > 0) {
        throw new IllegalStateException(
            postProcessor
                + " has a positive influence score ("
                + negInfluence
                + "), should not be positive");
      }

      if (Float.isInfinite(negInfluence) || Float.isInfinite(posInfluence)) {
        result = Float.POSITIVE_INFINITY;
        break;
      }

      result += Math.abs(negInfluence) + Math.abs(posInfluence);
    }

    return result;
  }
  /** {@inheritDoc} */
  @Override
  public Recommendations<OUT> doRecommend(IN input, Context<OUT, IN> context) {
    Recommendations<OUT> recommendations = new Recommendations<>();

    for (RecommendationEngine<OUT, IN> engine : engines) {
      if (engine.participationPolicy(context).participate(input, context, recommendations)) {
        recommendations.merge(engine.recommend(input, context));
      }
    }

    removeIrrelevant(input, context, recommendations);

    for (PostProcessor<OUT, IN> postProcessor : postProcessors) {
      postProcessor.postProcess(recommendations, input, context);
    }

    return recommendations;
  }