Search code examples
droolsoptaplanner

Rules are Ignored in VehicleRouting with TimeWindows


I took the example from Optaplanner to calculate optimal routes for a VRP with Timewindows and modefied the code a bit to satisfy our needs.

We have technicians who do work at a customers place. Each of the technicians is it own "depot" because they can start at diffrent locations. Each depot therefore has exactly one vehicle.

This works suprisingly well with the given example.

Now we want to add a rule that a technician should only be sheduled to a customer if he has the needed qualifications (for example only some technicians are allowed to drill)

The customer and the Technician both have qualifications that need to be matched. We added a Rule in the drl file:

rule "qulificationcorrect"
when
    $vehicle : Vehicle($vehicleQualifications:qualifications)
    $customer : Customer(vehicle==$vehicle,$customerQualification:qualifications)
    $test : Integer(RuleHelper.qualified($vehicleQualifications,$customerQualification)<1)
then
    System.out.println(RuleHelper.qualified($depotQualifications,$customerQualification));
    scoreHolder.addHardConstraintMatch(kcontext, -100L);

where Rulehelper looks like this:

public class RuleHelper
{
   public static int qualified(Integer[] vehicle, Integer[] customer)
   {
       if (customer == null || customer.length < 1)
       {
        System.out.println("CUSTOMER EMPTY");

        return 1;
       }
       if (vehicle == null || vehicle.length < 1)
       {
           System.out.println("VEHICLE EMPTY");

           return 0;
       }
       List<Integer> vehicleList = Arrays.asList(vehicle);
       List<Integer> customerList = Arrays.asList(customer);
       System.out.println("VehicleList=" + vehicleList + " CustomerList" + customerList + " rule = " + vehicleList.containsAll(customerList));
       return vehicleList.containsAll(customerList) ? 1 : 0;
   }
}

But when i look at my Solution it has no hardscore (0) and this is the output:

{
 "solutions" : [
   {
     "employee" : {
       "name" : "Technician 1",
       "qualifications" : [
         1,
         2,
         5,
         999
       ],
       "lat" : 49.70103,
       "lng" : 8.32404,
       "employeeId" : 31,
       "startTime" : 480,
       "endTime" : 1170,
       "maxOrderCount" : 0,
       "solutionId" : 1
     },
     "orders" : [
       {
         "qualifications" : [
           1000
         ],
         "transmittedStart" : 435,
         "transmittedEnd" : 660,
         "startTime" : 480,
         "endTime" : 540,
         "lat" : 49.96685,
         "lng" : 8.0308,
         "orderId" : 638411,
         "fixed" : false,
         "calculatedStart" : 522,
         "calculatedEnd" : 567,
         "solutionId" : 4
       },
       {
         "qualifications" : [
           999
         ],
         "transmittedStart" : 615,
         "transmittedEnd" : 840,
         "startTime" : 660,
         "endTime" : 720,
         "lat" : 49.89585,
         "lng" : 8.0809,
         "orderId" : 637001,
         "fixed" : false,
         "calculatedStart" : 583,
         "calculatedEnd" : 660,
         "solutionId" : 3
       }
     ]
   },
   {
     "employee" : {
       "name" : "Technician 2",
       "qualifications" : [
         3,
         1000
       ],
       "lat" : 49.70103,
       "lng" : 8.32404,
       "employeeId" : 264,
       "startTime" : 480,
       "endTime" : 1170,
       "maxOrderCount" : 0,
       "solutionId" : 2
     },
     "orders" : [ ]
   }
 ]
}

Basically the first technician gets both orders (customers) although he has only the qualifications 1,2,5,999 and not 1000. The scound technician gets no orders instead of having the one with qualification 1000.

I hope that's enough informations for someone to tell me whats wrong...

King regards

EDIT: Thanks to the help of Geoffrey De Smet I changed the Rule to

rule "qualificationcorrect"
when
    $customer : Customer(hasAllQualifications() == false)
then

    scoreHolder.addHardConstraintMatch(kcontext, -100L);

end

and also implemented hasAllQualifications in Customer:

    public boolean hasAllQualifications()
{
    if (qualifications.length < 1)
    {
            return true;
    }
    if (vehicle == null || vehicle.getQualifications() == null || vehicle.getQualifications().length < 1)
    { 
        return false;
    }
    List<Integer> vehicleList = Arrays.asList(vehicle.getQualifications());
    List<Integer> customerList = Arrays.asList(this.getQualifications());
    return vehicleList.containsAll(customerList);
}

Now the solution shows the expected behavoir Thanks again for the quick answer


Solution

  • It might be related to how "incremental score calculation" works (which relies on calling modify() on drools facts). Turn on environmentMode FULL_ASSERT - if it throws a score corruption exception this is the problem.

    If that's the case, let's take a look at your rule

    when
        $vehicle : Vehicle($vehicleQualifications:qualifications)
        // When the Customer.vehicle changes, the rule engine gets notified
        $customer : Customer(vehicle==$vehicle,$customerQualification:qualifications)
        // That should retrigger the evaluation of this line every time a customer's vehicle changes
        $test : Integer(RuleHelper.qualified($vehicleQualifications,$customerQualification)<1)
    

    In any case, you might want to rewrite it like this anyway:

    when
        $customer : Customer(hasAllQualifications() == false)
    

    And just have Customer.hasAllQualifications() look at Customer.vehicle