OptaPlanner: Make planning entities chained and nullable

隐身守侯 提交于 2020-01-05 03:56:07

问题


Dear OptaPlanner community

For a specific use case of the OptaPlanner framework, I would like to use the chained data structure, as it is used in the Vehicle Routing example. The problem in our case is that there are a lot of customers, and not all of them can be served in a specific planning cycle. For that reason, I thought using nullable planning variables could be useful, so not all tasks need to be assigned, while still having valid chains of Suppliers. My questions is, how could I solve this problem? Have an extra chain with unassigned tasks? Is there another way to circumvent this issue? Regards Raphael


回答1:


Chained planning variables don't support nullable=true yet (in version 7.8 at least).

In any case, I 'd apply continuous planning and start planning the next days too - the decisions on the next days might affect the decisions on the first day (for example presume you have 100 packets that needs to be delivered before day 2, but on day 2 half your drivers are on vacation and you don't have enough time to deliver them all on day 2 alone, so you need to deliver some on day 1 already.)

So, I 'd create VehicleDay which has a fixed Vehicle and Day - and I 'd use that everywhere were the example uses Vehicle now.




回答2:


I had a similar issue, where I needed to allow skipping of certain customers and minimize the number of vehicles used. I adapted the standard Optaplanner (7.12.0) VRP example by using a "Ghost" vehicle, like so:

@XStreamAlias("VrpGhostVehicle")
public class GhostVehicle extends Vehicle {
    @Override
    public Depot getDepot() {
        return null;
    }

    @Override
    public Long getId() {
        return Long.valueOf(0);
    }

    @Override
    public boolean isGhost() {
        return true;
    }

    @Override
    public int getCapacity() {
        return Integer.MAX_VALUE;
    }
}

To model customers that cannot be skipped, I added a "unskippable" boolean field to Customer.java. Then, in your vehicleRoutingScoreRules.drl, I adjusted and added constraints like:

rule "vehicleCapacity"
    when
        $vehicle : Vehicle(ghost == false, $capacity : capacity)
        accumulate(
            Customer(
                vehicle == $vehicle,
                $demand : demand);
            $demandTotal : sum($demand);
            $demandTotal > $capacity
        )
    then
        scoreHolder.addHardConstraintMatch(kcontext, $capacity - $demandTotal);
end

// ############################################################################
// Soft distance constraints
// ############################################################################

rule "distanceToPreviousStandstill"
    when
        $vehicle : Vehicle(ghost == false)
        $customer : Customer(previousStandstill != null, vehicle == $vehicle, $distanceFromPreviousStandstill : distanceFromPreviousStandstill)
    then
        scoreHolder.addSoftConstraintMatch(kcontext, - $distanceFromPreviousStandstill);
end

rule "distanceFromLastCustomerToDepot"
    when
        $vehicle : Vehicle(ghost == false)
        $customer : Customer(previousStandstill != null, vehicle == $vehicle)
        not Customer(previousStandstill == $customer)
    then
        Vehicle vehicle = $customer.getVehicle();
        scoreHolder.addSoftConstraintMatch(kcontext, - $customer.getDistanceTo(vehicle));
end


// ############################################################################
// Time window constraints 
// ############################################################################

rule "arrivalAfterDueTime"
    when
        $vehicle : Vehicle(ghost == false)
        TimeWindowedCustomer(dueTime < arrivalTime, vehicle == $vehicle, $dueTime : dueTime, $arrivalTime : arrivalTime)
    then
        scoreHolder.addSoftConstraintMatch(kcontext, -1000 * Math.abs($dueTime - $arrivalTime.longValue()));
end

rule "arrivalBeforeReadyTime"
    when
        $vehicle : Vehicle(ghost == false)
        TimeWindowedCustomer(readyTime > arrivalTime, vehicle == $vehicle, $readyTime : readyTime, $arrivalTime : arrivalTime)
    then
        scoreHolder.addSoftConstraintMatch(kcontext, -1000 * Math.abs($readyTime - $arrivalTime.longValue()));
end


// ############################################################################
// Constraints pertaining to station skipping and vehicle usage
// ############################################################################

rule "skippedCustomer"
    when
        $vehicle : Vehicle(ghost == true)
        $customer : Customer(vehicle == $vehicle, $unskippable : unskippable, $demand : demand)
    then
        if ($unskippable) {
            scoreHolder.addHardConstraintMatch(kcontext, -10000 * $demand);
        } else {
            scoreHolder.addSoftConstraintMatch(kcontext, -10000 * $demand);
        }
end

rule "usedTooManyVehicles"
    when
        $vehicle : Vehicle(nextCustomer != null, ghost == false)
    then
        scoreHolder.addSoftConstraintMatch(kcontext, -500000);
end

The bottom three rules are constraints I added to the example and I added further checks for ghost==false to the rules that should only apply to real vehicles. Notice that for unskippable customers we set a hard constraint whereas for skippable ones we set a soft constraint. The relative weights for skipping a customer or using a vehicle are of course application specific.



来源:https://stackoverflow.com/questions/51240290/optaplanner-make-planning-entities-chained-and-nullable

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!