I'm currently working on a Timefold solver based on the vehicle routing project (but largely modified). In my project, the soft constraint are:
I would like the manager of the solution to apply weight to each of these constraint in a "I would like to give the constraint of distance an importance of 90% where the other one is 10%" way because :
So I basically want to normalize my constraints and I think the documentation says it's not a good practice because of rounding and performance issues, but what if I normalize between Long.min and Long.max?
I would like to know how bad it looks and if it's not that bad, how to implement it properly in timefold.
It does not sound like simple, hard/soft nor Pareto scoring to me (maybe I'm wrong, let me know). Maybe a Hard/soft constraint with only the soft part being normalized is possible?
I tried to implement a Hard/Soft constraint with soft having coefficient, normalized by the sum of coefficient, but the result was not great (I had great Zs in my planification, which is far from being the optimal order of visits) and I have no idea why
Please let me know you if you have any ideas or suggestion and if I got anything wrong
You should still use Constraint Configuration to set the weight of each constraint, but expose a different API for your users that will be converted to a ConstraintConfiguration. In particular:
@ConstraintConfiguration
class to hold the weights, with a static method/constructor to create an instance from the model your user expects:@ConstraintConfiguration
public class VehicleRoutingConstraintConfiguration {
final Map<String, Long> weightMap;
public VehicleRoutingConstraintConfiguration() {
weightMap = new HashMap<>();
}
public VehicleRoutingConstraintConfiguration(Map<String, Long> weightMap) {
this.weightMap = weightMap;
}
@ConstraintWeight("Minimize distance")
public HardMediumSoftLongScore getMinimizeDistance() {
return HardMediumSoftLongScore.ofSoft(weightMap.getOrDefault("Minimize distance", 0L));
}
@ConstraintWeight("Maximize the duration of the visits")
public HardMediumSoftLongScore getMaximizeDuration() {
return HardMediumSoftLongScore.ofSoft(weightMap.getOrDefault("Maximize the duration of the visits", 0L));
}
public static VehicleRoutingConstraintConfiguration fromImportanceMap(Map<String, BigDecimal> importanceMap) {
// Step one: find the largest scale in values
int maxScale = 0;
for (var weight : importanceMap.values()) {
maxScale = Math.max(maxScale, weight.scale());
}
// Step two: Multiply all weights by 10^(maxScale) and convert to long
Map<String, Long> adjustedWeights = new HashMap<>();
for (var entry : importanceMap.entrySet()) {
adjustedWeights.put(entry.getKey(), entry.getValue().scaleByPowerOfTen(maxScale).longValueExact());
}
// For Map.of("Minimize distance, BigDecimal.valueOf(0.9),
// "Maximize duration of the visits", BigDecimal.valueOf(0.1))
// this will result in the constraints having weights 9 and 1
// respectively, making distance 9 times more important than
// visit duration.
//
// This means, assuming all other constraint matches are the
// same, that a solution that only reduces driving time by 1 hour
// would be preferred to a solution that only increases
// visit duration by 8 hours.
return new VehicleRoutingConstraintConfiguration(adjustedWeights);
}
}
@PlanningSolution
public class VehicleRoutingPlan {
@ConstraintConfigurationProvider
private VehicleRoutingConstraintConfiguration constraintConfiguration;
...
}
penalizeConfigurable
/rewardConfigurable
in your constraints:public class VehicleRoutingConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory factory) {
return new Constraint[] {
minimizeDistance(factory),
maximizeDuration(factory),
...
};
}
protected Constraint minimizeDistance(ConstraintFactory factory) {
return factory.forEach(...)
...
.penalizeConfigurable("Minimize distance", ...);
}
protected Constraint maximizeDuration(ConstraintFactory factory) {
return factory.forEach(...)
...
.penalizeConfigurable("Maximize the duration of the visits", ...);
}
...
}