簡體   English   中英

OptaPlanner 忽略約束

[英]OptaPlanner ignoring constraint

我在 VehicleRoutingConstraintProvider 中創建了一個timeToIdealDate約束。

  • 此約束的目標是將訪問車輛訪問分組,訪問的idealDate最接近車輛的start日期。

  • 我遇到的問題與分配給startidealDate的日期無關。 訪問似乎是隨機分配給車輛的,忽略了軟約束。

  • 我期望求解器將訪問從一輛車切換到另一輛車,直到分數變得越來越低,因為start日期更接近車輛的所有 idealDate` 的總和。

有任何想法嗎?

例子

鑒於我們有開始日期為2022-10-05的“車輛 A”和開始日期為2022-01-05的“車輛 B”。 理想日期為2022-11-23的訪問應分配給“車輛 B”。

示例數據集

  • 5 輛車( start日期分布在一年內)
  • 30 次訪問( idealDate在一年內隨機分布)

主要數據

PlanningVisit 有idealDate

  • 車輛訪問該位置的理想目標日期
  • 格式:unix 以秒為單位的時間戳

PlanningVehicle 已經start

  • 車輛離開站點的日期
  • 格式:unix 以秒為單位的時間戳

代碼片段

vehiclerouting/plugin/planner/VehicleRoutingConstraintProvider.java


package org.optaweb.vehiclerouting.plugin.planner;

import static org.optaplanner.core.api.score.stream.ConstraintCollectors.sumLong;

import org.optaplanner.core.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;
import org.optaweb.vehiclerouting.plugin.planner.domain.PlanningVisit;

public class VehicleRoutingConstraintProvider implements ConstraintProvider {

    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[] {
                vehicleCapacity(constraintFactory),
                timeToIdealDate(constraintFactory)
        };
    }

    Constraint vehicleCapacity(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(PlanningVisit.class)
                .groupBy(PlanningVisit::getVehicle, sum(PlanningVisit::getDemand))
                .filter((vehicle, demand) -> demand > vehicle.getCapacity())
                .penalizeLong(
                        "vehicle capacity",
                        HardMediumSoftLongScore.ONE_HARD,
                        (vehicle, demand) -> demand - vehicle.getCapacity());
    }

    Constraint timeToIdealDate(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(PlanningVisit.class)
                .penalizeLong(
                        "time to ideal date",
                        HardMediumSoftLongScore.ONE_SOFT,
                        PlanningVisit::absoluteDistanceToIdealDate);
    }
}

vehiclerouting/plugin/planner/domain/PlanningVisit.java

package org.optaweb.vehiclerouting.plugin.planner.domain;

import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.lookup.PlanningId;
import org.optaplanner.core.api.domain.variable.AnchorShadowVariable;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.optaplanner.core.api.domain.variable.PlanningVariableGraphType;
import org.optaweb.vehiclerouting.plugin.planner.weight.DepotAngleVisitDifficultyWeightFactory;

@PlanningEntity(difficultyWeightFactoryClass = DepotAngleVisitDifficultyWeightFactory.class)
public class PlanningVisit implements Standstill {

    @PlanningId
    private long id;
    private PlanningLocation location;
    private int idealDate;

    // Planning variable: changes during planning, between score calculations.
    @PlanningVariable(valueRangeProviderRefs = { "vehicleRange", "visitRange" },
            graphType = PlanningVariableGraphType.CHAINED)
    private Standstill previousStandstill;

    // Shadow variables
    private PlanningVisit nextVisit;
    @AnchorShadowVariable(sourceVariableName = "previousStandstill")
    private PlanningVehicle vehicle;

    PlanningVisit() {
        // Hide public constructor in favor of the factory.
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Override
    public PlanningLocation getLocation() {
        return location;
    }

    public void setLocation(PlanningLocation location) {
        this.location = location;
    }

    public int getIdealDate() {
        return idealDate;
    }

    public void setIdealDate(int idealDate) {
        this.idealDate = idealDate;
    }

    public Standstill getPreviousStandstill() {
        return previousStandstill;
    }

    public void setPreviousStandstill(Standstill previousStandstill) {
        this.previousStandstill = previousStandstill;
    }

    @Override
    public PlanningVisit getNextVisit() {
        return nextVisit;
    }

    @Override
    public void setNextVisit(PlanningVisit nextVisit) {
        this.nextVisit = nextVisit;
    }

    public PlanningVehicle getVehicle() {
        return vehicle;
    }

    public void setVehicle(PlanningVehicle vehicle) {
        this.vehicle = vehicle;
    }

    // ************************************************************************
    // Complex methods
    // ************************************************************************

    /**
     * Distance from the previous standstill to this visit. This is used to calculate the travel cost of a chain
     * beginning with a vehicle (at a depot) and ending with the {@link #isLast() last} visit.
     * The chain ends with a visit, not a depot so the cost of returning from the last visit back to the depot
     * has to be added in a separate step using {@link #distanceToDepot()}.
     *
     * @return distance from previous standstill to this visit
     */
    public long distanceFromPreviousStandstill() {
        if (previousStandstill == null) {
            throw new IllegalStateException(
                    "This method must not be called when the previousStandstill (null) is not initialized yet.");
        }
        return previousStandstill.getLocation().distanceTo(location);
    }

    /**
     * Distance from this visit back to the depot.
     *
     * @return distance from this visit back its vehicle's depot
     */
    public long distanceToDepot() {
        return location.distanceTo(vehicle.getLocation());
    }

    public long absoluteDistanceToIdealDate() {
        long vehicleStart = vehicle.getStart();
        if (idealDate > 0 && vehicleStart > 0) {
            return Math.abs(idealDate - vehicleStart);
        }

        return 0;
    }

    /**
     * Whether this visit is the last in a chain.
     *
     * @return true, if this visit has no {@link #getNextVisit() next} visit
     */
    public boolean isLast() {
        return nextVisit == null;
    }

    @Override
    public String toString() {
        return "PlanningVisit{" +
                (location == null ? "" : "location=" + location.getId()) +
                ",demand=" + demand +
                ",idealDate=" + idealDate +
                (previousStandstill == null ? "" : ",previousStandstill='" + previousStandstill.getLocation().getId()) +
                (nextVisit == null ? "" : ",nextVisit=" + nextVisit.getId()) +
                (vehicle == null ? "" : ",vehicle=" + vehicle.getId()) +
                ",id=" + id +
                '}';
    }
}

vehiclerouting/plugin/planner/domain/PlanningVehicle.java


package org.optaweb.vehiclerouting.plugin.planner.domain;

import java.util.Iterator;
import java.util.NoSuchElementException;

import org.optaplanner.core.api.domain.lookup.PlanningId;

public class PlanningVehicle implements Standstill {

    @PlanningId
    private long id;
    private int capacity;
    private int start;
    private PlanningDepot depot;

    // Shadow variables
    private PlanningVisit nextVisit;

    PlanningVehicle() {
        // Hide public constructor in favor of the factory.
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

   public int getCapacity() {
        return capacity;
    }

    public void setCapacity(int capacity) {
        this.capacity = capacity;
    }

    public int getStart() {
        return start;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public PlanningDepot getDepot() {
        return depot;
    }

    public void setDepot(PlanningDepot depot) {
        this.depot = depot;
    }

    @Override
    public PlanningVisit getNextVisit() {
        return nextVisit;
    }

    @Override
    public void setNextVisit(PlanningVisit nextVisit) {
        this.nextVisit = nextVisit;
    }

    public Iterable<PlanningVisit> getFutureVisits() {
        return () -> new Iterator<PlanningVisit>() {
            PlanningVisit nextVisit = getNextVisit();

            @Override
            public boolean hasNext() {
                return nextVisit != null;
            }

            @Override
            public PlanningVisit next() {
                if (nextVisit == null) {
                    throw new NoSuchElementException();
                }
                PlanningVisit out = nextVisit;
                nextVisit = nextVisit.getNextVisit();
                return out;
            }
        };
    }

    @Override
    public PlanningLocation getLocation() {
        return depot.getLocation();
    }

    @Override
    public String toString() {
        return "PlanningVehicle{" +
                "start=" + start +
                "capacity=" + capacity +
                (depot == null ? "" : ",depot=" + depot.getId()) +
                (nextVisit == null ? "" : ",nextVisit=" + nextVisit.getId()) +
                ",id=" + id +
                '}';
    }
}

timeToIdealDate約束看起來是正確的,盡管它可以通過刪除groupBy()並將PlanningVisit::absoluteDistanceToIdealDate直接傳遞給 penalizeLong penalizeLong() ) 來簡化:

Constraint timeToIdealDate(ConstraintFactory constraintFactory) {
        return constraintFactory.forEach(PlanningVisit.class)
                .penalizeLong(
                        "time to ideal date",
                        HardMediumSoftLongScore.ONE_SOFT,
                        PlanningVisit::absoluteDistanceToIdealDate);
    }
}

就調試約束而言,以下步驟可能有所幫助:

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM