Skip to content

Commit

Permalink
Added: time windows are now an extension point, allowing conditional …
Browse files Browse the repository at this point in the history
…time windows
  • Loading branch information
marcanpilami committed Sep 21, 2023
1 parent 2d4be9b commit b2b34a2
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.graphhopper.jsprit.core.problem.constraint.HardConstraint;
import com.graphhopper.jsprit.core.problem.constraint.HardRouteConstraint;
import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;

import java.util.ArrayList;
Expand All @@ -34,6 +35,7 @@
* Created by schroeder on 06/02/17.
*/
abstract class AbstractInsertionCalculator implements JobInsertionCostsCalculator {
protected TimeWindow defaultTimeWindow = TimeWindow.newInstance(0.0, Double.MAX_VALUE);

InsertionData checkRouteConstraints(JobInsertionContext insertionContext, ConstraintManager constraintManager) {
for (HardRouteConstraint hardRouteConstraint : constraintManager.getHardRouteConstraints()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,15 @@ public InsertionData getInsertionData(final VehicleRoute currentRoute, final Job
}
boolean not_fulfilled_break = true;
for(TimeWindow timeWindow : service.getTimeWindows()) {
deliveryAct2Insert.setTheoreticalEarliestOperationStartTime(timeWindow.getStart());
deliveryAct2Insert.setTheoreticalLatestOperationStartTime(timeWindow.getEnd());
ActivityContext activityContext = new ActivityContext();
activityContext.setInsertionIndex(actIndex);
insertionContext.setActivityContext(activityContext);
if (!timeWindow.isApplicable(insertionContext)) {
timeWindow = defaultTimeWindow;
}
deliveryAct2Insert.setTheoreticalEarliestOperationStartTime(timeWindow.getStart());
deliveryAct2Insert.setTheoreticalLatestOperationStartTime(timeWindow.getEnd());

ConstraintsStatus status = fulfilled(insertionContext, prevAct, deliveryAct2Insert, nextAct, prevActStartTime, failedActivityConstraints, constraintManager);
if (status.equals(ConstraintsStatus.FULFILLED)) {
double additionalICostsAtActLevel = softActivityConstraint.getCosts(insertionContext, prevAct, deliveryAct2Insert, nextAct, prevActStartTime);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,15 @@ public InsertionData getInsertionData(final VehicleRoute currentRoute, final Job

boolean pickupInsertionNotFulfilledBreak = true;
for(TimeWindow pickupTimeWindow : shipment.getPickupTimeWindows()) {
pickupShipment.setTheoreticalEarliestOperationStartTime(pickupTimeWindow.getStart());
pickupShipment.setTheoreticalLatestOperationStartTime(pickupTimeWindow.getEnd());
ActivityContext activityContext = new ActivityContext();
activityContext.setInsertionIndex(i);
insertionContext.setActivityContext(activityContext);
if (!pickupTimeWindow.isApplicable(insertionContext)) {
pickupTimeWindow = defaultTimeWindow;
}
pickupShipment.setTheoreticalLatestOperationStartTime(pickupTimeWindow.getEnd());
pickupShipment.setTheoreticalEarliestOperationStartTime(pickupTimeWindow.getStart());

ConstraintsStatus pickupShipmentConstraintStatus = fulfilled(insertionContext, prevAct, pickupShipment, nextAct, prevActEndTime, failedActivityConstraints, constraintManager);
if (pickupShipmentConstraintStatus.equals(ConstraintsStatus.NOT_FULFILLED)) {
pickupInsertionNotFulfilledBreak = false;
Expand Down Expand Up @@ -191,11 +195,15 @@ else if (pickupShipmentConstraintStatus.equals(ConstraintsStatus.FULFILLED)) {

boolean deliveryInsertionNotFulfilledBreak = true;
for (TimeWindow deliveryTimeWindow : shipment.getDeliveryTimeWindows()) {
deliverShipment.setTheoreticalEarliestOperationStartTime(deliveryTimeWindow.getStart());
deliverShipment.setTheoreticalLatestOperationStartTime(deliveryTimeWindow.getEnd());
ActivityContext activityContext_ = new ActivityContext();
activityContext_.setInsertionIndex(j);
insertionContext.setActivityContext(activityContext_);
if (!deliveryTimeWindow.isApplicable(insertionContext)) {
deliveryTimeWindow = defaultTimeWindow;
}
deliverShipment.setTheoreticalEarliestOperationStartTime(deliveryTimeWindow.getStart());
deliverShipment.setTheoreticalLatestOperationStartTime(deliveryTimeWindow.getEnd());

ConstraintsStatus deliverShipmentConstraintStatus = fulfilled(insertionContext, prevAct_deliveryLoop, deliverShipment, nextAct_deliveryLoop, prevActEndTime_deliveryLoop, failedActivityConstraints, constraintManager);
if (deliverShipmentConstraintStatus.equals(ConstraintsStatus.FULFILLED)) {
double additionalDeliveryICosts = softActivityConstraint.getCosts(insertionContext, prevAct_deliveryLoop, deliverShipment, nextAct_deliveryLoop, prevActEndTime_deliveryLoop);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/
package com.graphhopper.jsprit.core.problem.solution.route.activity;

import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext;

/**
* TimeWindow consists of a startTime and endTime.
*
Expand Down Expand Up @@ -116,5 +118,12 @@ public boolean equals(Object obj) {
return true;
}


/**
* Returns true if this time window is applicable in the current context. By default always true, only sub classes of TimeWindow may return false.
* @param time
* @return
*/
public boolean isApplicable(JobInsertionContext insertionContext) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.graphhopper.jsprit.core.problem.solution.route.activity;

import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext;

/**
* A time window that is only applicable for a specific type of vehicle.
* When using this, beware of vehicle switching.
*/
public class TimeWindowConditionalOnVehicleType extends TimeWindow {
private final String vehicleTypeId;

/**
* Returns new instance of TimeWindowConditionalOnVehicleType.
*
* @param start
* @param end
* @param vehicleTypeId
* @return TimeWindow
* @throw IllegalArgumentException either if start or end < 0.0 or end < start
*/
public static TimeWindow newInstance(double start, double end, String vehicleTypeId) {
return new TimeWindowConditionalOnVehicleType(start, end, vehicleTypeId);
}

public TimeWindowConditionalOnVehicleType(double start, double end, String vehicleTypeId) {
super(start, end);
this.vehicleTypeId = vehicleTypeId;
}

@Override
public boolean isApplicable(JobInsertionContext insertionContext) {
return insertionContext.getNewVehicle().getType().getTypeId().equals(vehicleTypeId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.graphhopper.jsprit.core.algorithm;

import org.junit.Assert;
import org.junit.Test;

import com.graphhopper.jsprit.core.algorithm.box.Jsprit;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.job.Service;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowConditionalOnVehicleType;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleType;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleTypeImpl;
import com.graphhopper.jsprit.core.util.Solutions;

/**
* Created by marcanpilami on 29/08/2023. This is a test of insertion mechanisms when using a conditional time window.
*/
public class IgnoreConditionalTimeWindowTest {

@Test
public void ignoreConditionalTimeWindow(){
VehicleTypeImpl.Builder vehicleTypeBuilder1 = VehicleTypeImpl.Builder.newInstance("vehicleType1");
VehicleTypeImpl.Builder vehicleTypeBuilder2 = VehicleTypeImpl.Builder.newInstance("vehicleType2");

VehicleType vehicleType1 = vehicleTypeBuilder1.build();
VehicleType vehicleType2 = vehicleTypeBuilder2.build();
/*
* get a vehicle-builder and build a vehicle located at (10,10) with type "vehicleType"
*/

VehicleImpl vehicle1,vehicle2;
{
VehicleImpl.Builder vehicleBuilder = VehicleImpl.Builder.newInstance("v1");
vehicleBuilder.setStartLocation(Location.newInstance(0, 0));
vehicleBuilder.setType(vehicleType1);
vehicleBuilder.setEarliestStart(10).setLatestArrival(50);

vehicle1 = vehicleBuilder.build();

vehicleBuilder = VehicleImpl.Builder.newInstance("v2");
vehicleBuilder.setStartLocation(Location.newInstance(0, 0));
vehicleBuilder.setType(vehicleType2);
vehicleBuilder.setEarliestStart(10).setLatestArrival(50);

vehicle2 = vehicleBuilder.build();
}

// First service is impossible for vehicle v1 (TW after vehicle return), but possible for all others which ignore the TW.
Service service1 = Service.Builder.newInstance("s1").setLocation(Location.newInstance(0, 0))
.setServiceTime(1.).setTimeWindow(TimeWindowConditionalOnVehicleType.newInstance(100,100, "vehicleType1")).build();

VehicleRoutingProblem vrp1 = VehicleRoutingProblem.Builder.newInstance()
.addVehicle(vehicle1)
.addJob(service1)
.setFleetSize(VehicleRoutingProblem.FleetSize.FINITE)
.build();

VehicleRoutingAlgorithm vra1 = Jsprit.createAlgorithm(vrp1);
vra1.setMaxIterations(50);
VehicleRoutingProblemSolution solution1 = Solutions.bestOf(vra1.searchSolutions());

Assert.assertEquals(1, solution1.getUnassignedJobs().size());

// Second service is possible for v1.
Service service2 = Service.Builder.newInstance("s2").setLocation(Location.newInstance(0, 0))
.setServiceTime(1.).setTimeWindow(TimeWindowConditionalOnVehicleType.newInstance(11, 11, "vehicleType1")).build();

VehicleRoutingProblem vrp2 = VehicleRoutingProblem.Builder.newInstance()
.addVehicle(vehicle1)
.addJob(service1)
.addJob(service2)
.setFleetSize(VehicleRoutingProblem.FleetSize.FINITE)
.build();

VehicleRoutingAlgorithm vra2 = Jsprit.createAlgorithm(vrp2);
vra2.setMaxIterations(50);
VehicleRoutingProblemSolution solution2 = Solutions.bestOf(vra2.searchSolutions());

Assert.assertEquals(1, solution2.getUnassignedJobs().size());
Assert.assertEquals(1, solution2.getRoutes().size());
Assert.assertEquals(1, solution2.getRoutes().iterator().next().getActivities().size());

// First service should be possible for v2.
VehicleRoutingProblem vrp3 = VehicleRoutingProblem.Builder.newInstance()
.addVehicle(vehicle2)
.addJob(service1)
.setFleetSize(VehicleRoutingProblem.FleetSize.FINITE)
.build();

VehicleRoutingAlgorithm vra3 = Jsprit.createAlgorithm(vrp3);
vra3.setMaxIterations(50);
VehicleRoutingProblemSolution solution3 = Solutions.bestOf(vra3.searchSolutions());

Assert.assertEquals(0, solution3.getUnassignedJobs().size());
}
}

0 comments on commit b2b34a2

Please sign in to comment.