Search code examples
javaconstraintsoptaplanner

Custom "sumDouble()" function in OptaPlanner for constraints


I need a sum functionality that sums up double values for my ConstraintProviding functionality. Currently OptaPlanner offers sum() and sumBigDecimal() functionality, where the first is only summing integer values and the second BigDecimal values. So therefore I started with the compose approach as described in the manual chapter 6.4.5.3. for implementing my own functionality (didn't want to override the original one).

Deriving from there and taking the implementation of the sum functionality from the ConstraintCollector.java class of the OptaPlanner source code itself, I ended up with the following code:

    public static <A> UniConstraintCollector<A, ?, Double> sumDouble(ToDoubleFunction<A> groupValueMapping) {
            return compose((resultContainer, a) -> {
                        double value = groupValueMapping.applyAsDouble(a);
                        resultContainer[0] += value;
                        return () -> resultContainer[0] -= value;
                    },
                    resultContainer -> resultContainer[0]);
        }

within my "OwnConstraintProvider" class. But this doesn't work out. The error is:

    java: method compose in interface java.lang.module.ModuleFinder cannot be applied to given types;
  required: java.lang.module.ModuleFinder[]
  found: (resultCon[...]ue; },(resultCon[...]er[0]
  reason: varargs mismatch; java.lang.module.ModuleFinder is not a functional interface
      multiple non-overriding abstract methods found in interface java.lang.module.ModuleFinder

I am aware that there must be a clearer relationship and calculation approach between the input A and the result.

Frankly speaking I have only recently starting Java programming seriously. So I can't sort out where I am mistaken in that case. In the manual of the current version used (8.19.0) there is "a generic sum() variant for summing up custom types" mentioned in chapter 6.4.5.1.3. but I have no glue about the details on that.

Can anybody give me a hint on this please.

Thanks in advance!


Solution

  • First of all, Radovan is completely correct in his answer. In fact, the potential score corruptions are the reason why sumDouble() is not provided. Instead, we provide sumBigDecimal(), which doesn't suffer from the same issue. However, it will suffer in terms of performance. The preferred solution is to use either sum() or sumLong(), using fixed-point arithmetic if necessary.

    That said, implementing sumDouble() is relatively simple, and you do not need composition to achieve that:

    public static <A> UniConstraintCollector<A, ?, Double> sum(ToDoubleFunction<? super A> groupValueMapping) {
        return new DefaultUniConstraintCollector<>(
            () -> new double[1],
            (resultContainer, a) -> {
                int value = groupValueMapping.applyAsDouble(a);
                resultContainer[0] += value;
                return () -> resultContainer[0] -= value;
            },
            resultContainer -> resultContainer[0]);
    }
    

    Now, DefaultUniConstraintCollector is not a public type. But you can use an anonymous class instead:

    public static <A> UniConstraintCollector<A, ?, Integer> sum(ToDoubleFunction<? super A> groupValueMapping) {
        return new UniConstraintCollector<A, double[], Double>() {
            @Override
            public Supplier<double[]> supplier() {
                return () -> new double[1];
            }
    
            @Override
            public BiFunction<double[], A, Runnable> accumulator() {
                return (resultContainer, a) -> {
                    double value = groupValueMapping.applyAsDouble(a);
                    resultContainer[0] += value;
                    return () -> resultContainer[0] -= value;
                };
            }
            
            @Override
            public Function<double[], Double> finisher() {
                return resultContainer -> resultContainer[0];
            }
        }
    }
    

    Use this at your own risk, and make sure you check for score corruptions, preferrably in a very long solver run.