繁体   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