使用链变量监听器类时在Optaplanner中获取NPE

问题描述 投票:0回答:1

我有一个 java spring boot 后端应用程序,它将从 mongo 获取无人认领的访问,然后使用 optaplanner 找到覆盖提供商/临床医生的这些访问的最佳路线。在我的约束提供者类中也有一些类似于 VRP 的约束。问题是我在变量更改侦听器中收到 NPE,因为出发时间为空,即使 previousVisit 不为空也是如此。

Visits previousVisit = visit.getPreviousVisit();
        LocalDateTime departureTime =
                previousVisit == null ? visit.getAssignedClinician().getDepartureTime() : previousVisit.getDepartureTime();
        Visits nextVisit = visit;
        LocalDateTime arrivalTime = previousVisit == null ?
                departureTime.plusMinutes(getDrivingTime(visit.getAssignedClinician().getLocation().getCoordinates(), visit.getMember().getMemberLocation().getCoordinates())) :
                departureTime.plusMinutes(getDrivingTime(previousVisit.getMember().getMemberLocation().getCoordinates(), nextVisit.getMember().getMemberLocation().getCoordinates()));

**注意:由于我想安排访问而不是对提供商的日程进行实时更改,我的用例是否需要变量更改侦听器?如果没有,请让我知道我在哪里犯了错误或者设计是否需要更改。谢谢并非常感谢。 **

下面是我的后端应用程序的基本用例。

用例:

临床医生/提供者一天内有空,会员也有空,提供者时段可以分为 1 小时时段。提供者将从家里开始进行访问。我们需要为提供商找到最佳的访问者。下面是示例。这是带有附加约束的车辆路径问题时间窗口。

现在我们需要检查的不是距离,而是驾驶时间和 1 小时的访问时段,所以假设提供商时段为上午 8 点至 11 点,平均访问时间为 55 分钟,会员的访问窗口为上午 8 点至 10 点,那么提供商应到达会员位置在上午 8 点至 10 点之间,并在该时间范围内完成 55 分钟的访问,所以基本上这是车辆路线问题,涉及提供商和会员的时间窗口,并考虑驾驶时间和访问时间。因此,我们将使用 Mapbox api 来计算行驶时间,并假设访问时间为 55 分钟,而不是半正矢距离。

以下示例路线供提供商澄清更多信息:

提供商时段:上午 8 点至 11 点

访问 1(时间窗口上午 8 点至上午 10 点):开车时间 20 分钟,早上 7:40 离开家,早上 8 点到达并访问 55 分钟,即直到上午 8:55(在访问时间窗口内)分配的提供商时段:上午 8 点至上午 9 点

访问 1 访问 2(时间窗口 9-11am):车程 20 分钟,于上午 9:15 到达并访问 55 分钟,即直到上午 10:05(在访问时间窗口内)分配的提供商时段:上午 9-10 点

访问 2 访问 3(时间窗口 10-11am):车程 20 分钟,上午 10:25 到达并访问 55 分钟,即到上午 11:20(在访问时间窗口内),因此不应分配此访问至提供商 未分配提供商插槽

规划单位1:

@PlanningEntity
@Data
public class Visits {
    private String _id;
    private Double matrixVisitId;
    private Long clientId;
    private VisitMsa msa;
    private Date[] slotPreferences;
    private String[] products;
    private VisitsMember member;
    private String slotType;
    private boolean pinned;
    private TimeWindow timeWindow;

    private ClinicianSlot assignedSlot;

    private Providers assignedClinician;
    private Visits previousVisit;
    private Visits nextVisit;

    private LocalDateTime arrivalTime;

    @ValueRangeProvider(id = "timeBlockRangeForVisit")
    public List<ClinicianSlot> generatePossibleTimeBlocks() {
        if (this.timeWindow == null) {
            return Collections.emptyList(); // Return an empty list for home visit
        }
        List<ClinicianSlot> possibleTimeBlocks = new ArrayList<>();
        LocalDateTime start = timeWindow.getStart();
        while (!start.plusHours(1).isAfter(timeWindow.getEnd())) {
            possibleTimeBlocks.add(new ClinicianSlot(start, start.plusHours(1)));
            start = start.plusHours(1);
        }
        return possibleTimeBlocks;
    }

    @PlanningPin
    public boolean isPinned() {
        return pinned;
    }

    @PreviousElementShadowVariable(sourceVariableName = "visits")
    public Visits getPreviousVisit() {
        return previousVisit;
    }

    public void setPreviousVisit(Visits previousVisit) {
        this.previousVisit = previousVisit;
    }

    @NextElementShadowVariable(sourceVariableName = "visits")
    public Visits getNextVisit() {
        return nextVisit;
    }

    public void setNextVisit(Visits nextVisit) {
        this.nextVisit = nextVisit;
    }

    @InverseRelationShadowVariable(sourceVariableName = "visits")
    public Providers getAssignedClinician() {
        return assignedClinician;
    }

    public void setVehicle(Providers assignedClinician) {
        this.assignedClinician = assignedClinician;
    }

    @ShadowVariable(variableListenerClass = VisitChainVariableListener.class, sourceVariableName = "previousVisit")
    public LocalDateTime getArrivalTime() {
        return arrivalTime;
    }

    public void setArrivalTime(LocalDateTime arrivalTime) {
        this.arrivalTime = arrivalTime;
    }

    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    public LocalDateTime getDepartureTime() {
        if (arrivalTime == null) {
            return null;
        }
        return getStartServiceTime().plusMinutes(55);
    }

    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    public LocalDateTime getStartServiceTime() {
        if (arrivalTime == null) {
            return null;
        }
        return arrivalTime.isBefore(minStartTime) ? minStartTime : arrivalTime;
    }
}

**规划实体2:**

@Data
@PlanningEntity
@AllArgsConstructor
@NoArgsConstructor
public class Providers {
    private Integer staffResourceId;
    private String providerId;
    private ProviderMsa[] msa;
    private Client[] clients;
    private String[] products;
    private List<ClinicianSlot> availableSlots;

    @PlanningListVariable(valueRangeProviderRefs = "visitRange")
    private List<Visits> visits = new ArrayList<>();

    @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
    private Location location;

    private LocalDateTime departureTime;
}

解决方案类:

@PlanningSolution
@Data
public class ProviderRouteSolution {

    private List<Visits> visitList;

    @ProblemFactCollectionProperty
    @ValueRangeProvider(id = "visitRange")
    public List<Visits> getVisitList() {
        return visitList;
    }

    private List<Providers> clinicianList;
    
    @PlanningScore
    private HardSoftScore score;

    @ValueRangeProvider(id = "clinicianRange")
    @PlanningEntityCollectionProperty
    public List<Providers> getClinicianList() {
        return clinicianList;
    }
}

变量变化监听器类:

public class VisitChainVariableListener implements VariableListener<ProviderRouteSolution, Visits> {

    @Override
    public void afterVariableChanged(ScoreDirector<ProviderRouteSolution> scoreDirector, Visits visit) {
        if (visit.getAssignedClinician() == null) {
            if (visit.getArrivalTime() != null) {
                scoreDirector.beforeVariableChanged(visit, "arrivalTime");
                visit.setArrivalTime(null);
                scoreDirector.afterVariableChanged(visit, "arrivalTime");
            }
            return;
        }
        Visits previousVisit = visit.getPreviousVisit();
        LocalDateTime departureTime =
                previousVisit == null ? visit.getAssignedClinician().getDepartureTime() : previousVisit.getDepartureTime();
        Visits nextVisit = visit;
        LocalDateTime arrivalTime = previousVisit == null ?
                departureTime.plusMinutes(getDrivingTime(visit.getAssignedClinician().getLocation().getCoordinates(), visit.getMember().getMemberLocation().getCoordinates())) :
                departureTime.plusMinutes(getDrivingTime(previousVisit.getMember().getMemberLocation().getCoordinates(), nextVisit.getMember().getMemberLocation().getCoordinates()));
        while (nextVisit != null && !Objects.equals(nextVisit.getArrivalTime(), arrivalTime)) {
            scoreDirector.beforeVariableChanged(nextVisit, "arrivalTime");
            nextVisit.setArrivalTime(arrivalTime);
            scoreDirector.afterVariableChanged(nextVisit, "arrivalTime");
            departureTime = nextVisit.getDepartureTime();
            Double[] departureCoordinates = nextVisit.getMember().getMemberLocation().getCoordinates();
            nextVisit = nextVisit.getNextVisit();
            arrivalTime = departureTime.plusMinutes(getDrivingTime(departureCoordinates, visit.getMember().getMemberLocation().getCoordinates()));
        }
    }
}

提供商路线服务:

public List<String> getProviderRouteForTheDay(int staffResourceId, double radiusInMiles, String visitDateString) throws ParseException {
        Providers provider = providersRepository.findByStaffResourceId(staffResourceId);
        Double longitude = provider.getLocation().getCoordinates()[0];
        Double latitude = provider.getLocation().getCoordinates()[1];
        double radiusInMeters = radiusInMiles * 1609.34;
        Location location = new Location(latitude, longitude);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        Date visitDate = dateFormat.parse(visitDateString);

        Calendar startCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
        startCal.setTime(visitDate);
        startCal.set(Calendar.HOUR_OF_DAY, 0);
        startCal.set(Calendar.MINUTE, 0);
        startCal.set(Calendar.SECOND, 0);
        startCal.set(Calendar.MILLISECOND, 0);
        Date startOfDay = startCal.getTime();

        Calendar endCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
        endCal.setTime(visitDate);
        endCal.set(Calendar.HOUR_OF_DAY, 23);
        endCal.set(Calendar.MINUTE, 59);
        endCal.set(Calendar.SECOND, 59);
        endCal.set(Calendar.MILLISECOND, 999);
        Date endOfDay = endCal.getTime();
        System.out.println("Start of Day: " + startOfDay);
        System.out.println("End of Day: " + endOfDay);
        List<ProviderSlots> providerSlots = providerSlotsRepository.findSlotsByProviderAndDateRangeAndStatus(provider.getStaffResourceId(), startOfDay, endOfDay, "available");
        List<ClinicianSlot> clinicianSlots = new ArrayList<>();
        for (ProviderSlots slot : providerSlots) {
            clinicianSlots.addAll(generate1HourSlots(convertToLocalDateTime(slot.getSlotTime()), convertToLocalDateTime(slot.getSlotEndTime())));
        }
        provider.setAvailableSlots(clinicianSlots);
        provider.setDepartureTime(LocalDateTime.of(
                LocalDateTime.now(ZoneOffset.UTC).getYear(),
                LocalDateTime.now(ZoneOffset.UTC).getMonth(),
                LocalDateTime.now(ZoneOffset.UTC).getDayOfMonth(),
                12, 0
        ));
        List<Visits> visitsList = visitsRepository.findUnclaimedVisitsNear(location.getLongitude(), location.getLatitude(), radiusInMeters, startOfDay, endOfDay);
        assignVisitTimeWindows(visitsList);
        ProviderRouteSolution unsolvedSolution = new ProviderRouteSolution();
        unsolvedSolution.setVisitList(visitsList);
        unsolvedSolution.setClinicianList(Collections.singletonList(provider));
        initializeDrivingTimeMatrix(provider, visitsList);
        try {
            ProviderRouteSolution solvedSolution = solver.solve(unsolvedSolution);
        } catch(Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

我想找到提供商的最佳访问路线,在变量链侦听器类中获得 NPE。我不确定我的实施是否正确,因为我不是在寻找 optaweb-VRP,我正在寻找链接访问以找到提供商的优化路径。

optaplanner vehicle-routing timefold optaweb-vehicle-routing
1个回答
0
投票

在最新版本之一中,实现影子变量侦听器的机制已得到极大简化。您可以在官方网站上查看有关该内容的博客文章。

对于示例中的使用,快速入门已进行了相应调整。我认为几乎您需要的所有内容都将包含在

找到的车辆路线示例中。这包括通过 @PlanningListVariable 注释进行链式访问。在快速入门中,此处使用了该注释。

© www.soinside.com 2019 - 2024. All rights reserved.