Skip to content

Commit

Permalink
Add late diversion constraint for drt detours (#2455)
Browse files Browse the repository at this point in the history
* fixed approach time for drt dropoffs

* allowDetourBeforeArrivalThreshold

* break early when following stops are above the threshold

* catch case when there are no scheduled stops

* minor config updates

* fix bug

* add test for late detour prohibition

* add one more test

* update detour before arrival threshold

* address review comments

* simplify method, rename to lateDiversionThreshold

* rename method
  • Loading branch information
nkuehnel authored May 30, 2024
1 parent d5b1700 commit 870a0f7
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ public EmptyVehicleChargingScheduler get() {
getter.getModal(PassengerStopDurationProvider.class)))).asEagerSingleton();

bindModal(InsertionCostCalculator.class).toProvider(modalProvider(
getter -> new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class))));
getter -> new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class),
drtCfg.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet())));

install(DrtModeOptimizerQSimModule.getInsertionSearchQSimModule(drtCfg));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ shiftsParams, new DefaultShiftStartLogic(), new DefaultAssignShiftToVehicleLogic

bindModal(InsertionCostCalculator.class).toProvider(modalProvider(
getter -> new ShiftInsertionCostCalculator(getter.get(MobsimTimer.class),
new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class)))));
new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class),
drtCfg.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet()))));

bindModal(VehicleEntry.EntryFactory.class).toInstance(new ShiftVehicleDataEntryFactory(new VehicleDataEntryFactoryImpl()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ protected void configureQSim() {
getter.getModal(PassengerStopDurationProvider.class)))).asEagerSingleton();

bindModal(InsertionCostCalculator.class).toProvider(modalProvider(
getter -> new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class))));
getter -> new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class),
drtCfg.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet())));

install(getInsertionSearchQSimModule(drtCfg));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ public DrtOptimizationConstraintsSet() {
@PositiveOrZero // used only for stopbased DRT scheme
public double maxWalkDistance = Double.MAX_VALUE;// [m];

@Parameter
@Comment(
"Time before reaching a planned dropoff from which it is not allowed to insert new detours for new requests. I.e.," +
" if set to 180, then a vehicle will not divert to pickup or dropoff a new passenger once a boarded passenger is only " +
"3 minutes away from her destination, even though her time window would allow it." +
" Delayed detours just before arrival are usually perceived very negatively.")
@PositiveOrZero
public double lateDiversionthreshold = 0; // [s];

@Override
protected void checkConsistency(Config config) {
super.checkConsistency(config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,25 @@

package org.matsim.contrib.drt.optimizer.insertion;

import static org.matsim.contrib.drt.optimizer.insertion.InsertionGenerator.Insertion;

import org.matsim.contrib.drt.optimizer.DrtOptimizationConstraintsSet;
import org.matsim.contrib.drt.optimizer.VehicleEntry;
import org.matsim.contrib.drt.optimizer.Waypoint;
import org.matsim.contrib.drt.optimizer.insertion.InsertionDetourTimeCalculator.DetourTimeInfo;
import org.matsim.contrib.drt.passenger.DrtRequest;

import static org.matsim.contrib.drt.optimizer.insertion.InsertionGenerator.Insertion;

/**
* @author michalm
*/
public class DefaultInsertionCostCalculator implements InsertionCostCalculator {
private final CostCalculationStrategy costCalculationStrategy;
private final DrtOptimizationConstraintsSet constraintsSet;

public DefaultInsertionCostCalculator(CostCalculationStrategy costCalculationStrategy) {
public DefaultInsertionCostCalculator(CostCalculationStrategy costCalculationStrategy,
DrtOptimizationConstraintsSet constraintsSet) {
this.costCalculationStrategy = costCalculationStrategy;
this.constraintsSet = constraintsSet;
}

/**
Expand Down Expand Up @@ -67,6 +73,12 @@ public double calculate(DrtRequest drtRequest, Insertion insertion, DetourTimeIn
return INFEASIBLE_SOLUTION_COST;
}

if (vEntry.stops != null && !vEntry.stops.isEmpty() && constraintsSet.lateDiversionthreshold > 0) {
if(violatesLateDiversion(insertion, detourTimeInfo, vEntry, effectiveDropoffTimeLoss)) {
return INFEASIBLE_SOLUTION_COST;
}
}

return costCalculationStrategy.calcCost(drtRequest, insertion, detourTimeInfo);
}

Expand All @@ -75,4 +87,35 @@ private boolean violatesMaxRideDuration(DrtRequest drtRequest, InsertionDetourTi
double rideDuration = detourTimeInfo.dropoffDetourInfo.arrivalTime - detourTimeInfo.pickupDetourInfo.departureTime;
return drtRequest.getMaxRideDuration() < rideDuration;
}
}

private boolean violatesLateDiversion(Insertion insertion, DetourTimeInfo detourTimeInfo,
VehicleEntry vEntry, double effectiveDropoffTimeLoss) {
if (detourTimeInfo.pickupDetourInfo.pickupTimeLoss > 0) {
if (violatesLateDiversionBetweenStopIndices(vEntry, insertion.pickup.index, insertion.dropoff.index,
constraintsSet.lateDiversionthreshold)) {
return true;
}
}
if (effectiveDropoffTimeLoss > 0) {
if (violatesLateDiversionBetweenStopIndices(vEntry, insertion.dropoff.index, vEntry.stops.size(),
constraintsSet.lateDiversionthreshold)) {
return true;
}
}
return false;
}

private boolean violatesLateDiversionBetweenStopIndices(VehicleEntry vehicleEntry, int start, int end, double lateDiversionThreshold) {
for (int s = start; s < end; s++) {
Waypoint.Stop stop = vehicleEntry.stops.get(s);
if (!stop.task.getDropoffRequests().isEmpty()) {
double remainingRideDuration = stop.getArrivalTime() - vehicleEntry.start.getDepartureTime();
if (remainingRideDuration < lateDiversionThreshold) {
return true;
}
break;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,25 @@

package org.matsim.contrib.drt.optimizer.insertion;

import static org.assertj.core.api.Assertions.assertThat;
import static org.matsim.contrib.drt.optimizer.insertion.InsertionCostCalculator.INFEASIBLE_SOLUTION_COST;
import static org.matsim.contrib.drt.optimizer.insertion.InsertionDetourTimeCalculator.*;

import com.google.common.collect.ImmutableList;
import org.junit.jupiter.api.Test;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.contrib.drt.optimizer.DrtOptimizationConstraintsSet;
import org.matsim.contrib.drt.optimizer.VehicleEntry;
import org.matsim.contrib.drt.optimizer.Waypoint;
import org.matsim.contrib.drt.optimizer.insertion.InsertionGenerator.Insertion;
import org.matsim.contrib.drt.passenger.AcceptedDrtRequest;
import org.matsim.contrib.drt.passenger.DrtRequest;
import org.matsim.contrib.drt.run.DrtConfigGroup;
import org.matsim.contrib.drt.schedule.DefaultDrtStopTask;
import org.matsim.contrib.drt.schedule.DrtStopTask;
import org.matsim.testcases.fakes.FakeLink;

import static org.assertj.core.api.Assertions.assertThat;
import static org.matsim.contrib.drt.optimizer.insertion.InsertionCostCalculator.INFEASIBLE_SOLUTION_COST;
import static org.matsim.contrib.drt.optimizer.insertion.InsertionDetourTimeCalculator.*;

/**
* @author Michal Maciejewski (michalm)
*/
Expand All @@ -42,36 +49,141 @@ public class InsertionCostCalculatorTest {

@Test
void testCalculate() {
VehicleEntry entry = entry(new double[] { 20, 20, 50 });
VehicleEntry entry = entry(new double[] { 20, 20, 50 }, ImmutableList.<Waypoint.Stop>builder().build(), null);
var insertion = insertion(entry, 0, 1);

//feasible solution
final DrtConfigGroup drtConfigGroup = new DrtConfigGroup();
assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(0, 11), new DropoffDetourInfo(0, 22)),
11 + 22);
11 + 22, drtRequest, drtConfigGroup.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet());

//feasible solution - longest possible pickup and dropoff time losses
final DrtConfigGroup drtConfigGroup1 = new DrtConfigGroup();
assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(0, 20), new DropoffDetourInfo(0, 30)),
20 + 30);
20 + 30, drtRequest, drtConfigGroup1.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet());

//infeasible solution - time constraints at stop 0
final DrtConfigGroup drtConfigGroup2 = new DrtConfigGroup();
assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(0, 21), new DropoffDetourInfo(0, 29)),
INFEASIBLE_SOLUTION_COST);
INFEASIBLE_SOLUTION_COST, drtRequest, drtConfigGroup2.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet());

//infeasible solution - vehicle time constraints
final DrtConfigGroup drtConfigGroup3 = new DrtConfigGroup();
assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(0, 20), new DropoffDetourInfo(0, 31)),
INFEASIBLE_SOLUTION_COST);
INFEASIBLE_SOLUTION_COST, drtRequest, drtConfigGroup3.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet());
}


@Test
public void testAllowDetourBeforeArrivalThreshold() {


// start (0s) -----> new PU (60s) -----> existing DO (120s) -----> new DO (300s)

Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 1);

DrtStopTask existingDropoffTask = new DefaultDrtStopTask(120, 150, link("boardedDO"));
DrtRequest boardedRequest = DrtRequest.newBuilder().fromLink(link("boardedFrom")).toLink(link("boardedTo")).build();

AcceptedDrtRequest existingRequest = AcceptedDrtRequest.createFromOriginalRequest(boardedRequest);
existingDropoffTask.addDropoffRequest(existingRequest);

Waypoint.Stop[] stops = new Waypoint.Stop[1];
stops[0] = new Waypoint.Stop(existingDropoffTask, 0);

VehicleEntry entry = entry(new double[] {60, 60, 300}, ImmutableList.copyOf(stops), start);
var insertion = insertion(entry, 0, 1);

DrtRequest drtRequest = DrtRequest.newBuilder()
.fromLink(fromLink)
.toLink(toLink)
.latestStartTime(120)
.latestArrivalTime(300)
.maxRideDuration(Double.MAX_VALUE)
.build();

DrtConfigGroup drtConfigGroup = new DrtConfigGroup();
DrtOptimizationConstraintsSet drtOptimizationConstraintsSet = drtConfigGroup.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet();

// new insertion before dropoff of boarded passenger within threshold - infeasible solution
drtOptimizationConstraintsSet.lateDiversionthreshold = 180;
assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(60, 30), new DropoffDetourInfo(300, 30)),
INFEASIBLE_SOLUTION_COST, drtRequest, drtOptimizationConstraintsSet);

// new insertion before dropoff of boarded passenger, inside of threshold but no additional delay - feasible solution
assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(120, 0), new DropoffDetourInfo(300, 30)),
30, drtRequest, drtOptimizationConstraintsSet);

// new insertion before dropoff of boarded passenger, but outside of threshold - feasible solution
drtOptimizationConstraintsSet.lateDiversionthreshold = 120;
assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(60, 30), new DropoffDetourInfo(300, 30)),
60, drtRequest, drtOptimizationConstraintsSet);


// new insertion after dropoff of boarded passenger - feasible solution
insertion = insertion(entry, 1, 1);
assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(60, 30), new DropoffDetourInfo(300, 30)),
60, drtRequest, drtOptimizationConstraintsSet);
}

@Test
public void testAllowDetourBeforeArrivalThreshold2() {

// start (0s) -----> new PU (60s) -----> existing PU (120s) -----> existing DO (200s) -----> new DO (300s)

Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 0);

DrtStopTask existingPickupTask = new DefaultDrtStopTask(120, 150, link("scheduledPU"));
DrtRequest scheduledRequest = DrtRequest.newBuilder().fromLink(link("scheduledFrom")).toLink(link("scheduledTo")).build();
AcceptedDrtRequest acceptedScheduledRequest = AcceptedDrtRequest.createFromOriginalRequest(scheduledRequest);
existingPickupTask.addPickupRequest(acceptedScheduledRequest);

DrtStopTask existingDropoffTask = new DefaultDrtStopTask(200, 230, link("boardedDO"));
DrtRequest boardedRequest = DrtRequest.newBuilder().fromLink(link("boardedFrom")).toLink(link("boardedTo")).build();
AcceptedDrtRequest existingRequest = AcceptedDrtRequest.createFromOriginalRequest(boardedRequest);
existingDropoffTask.addDropoffRequest(existingRequest);

Waypoint.Stop[] stops = new Waypoint.Stop[2];
stops[0] = new Waypoint.Stop(existingPickupTask, 2);
stops[1] = new Waypoint.Stop(existingDropoffTask, 1);

VehicleEntry entry = entry(new double[] {60, 60, 60, 300}, ImmutableList.copyOf(stops), start);


var insertion = insertion(entry, 0, 2);

DrtRequest drtRequest = DrtRequest.newBuilder()
.fromLink(fromLink)
.toLink(toLink)
.latestStartTime(120)
.latestArrivalTime(300)
.maxRideDuration(Double.MAX_VALUE)
.build();

DrtConfigGroup drtConfigGroup = new DrtConfigGroup();

// new insertion before dropoff of boarded passenger within threshold - infeasible solution
DrtOptimizationConstraintsSet constraintsSet = drtConfigGroup.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet();
constraintsSet.lateDiversionthreshold = 300;
assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(60, 60), new DropoffDetourInfo(300, 60)),
INFEASIBLE_SOLUTION_COST, drtRequest, constraintsSet);

// new insertion before dropoff of boarded passenger outside of threshold - feasible solution
constraintsSet.lateDiversionthreshold = 200;
assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(60, 60), new DropoffDetourInfo(300, 60)),
120, drtRequest, constraintsSet);
}

private void assertCalculate(Insertion insertion, DetourTimeInfo detourTimeInfo, double expectedCost) {
private void assertCalculate(Insertion insertion, DetourTimeInfo detourTimeInfo, double expectedCost, DrtRequest drtRequest, DrtOptimizationConstraintsSet constraintsSet) {
var insertionCostCalculator = new DefaultInsertionCostCalculator(
new CostCalculationStrategy.RejectSoftConstraintViolations());
new CostCalculationStrategy.RejectSoftConstraintViolations(), constraintsSet);
var insertionWithDetourData = new InsertionWithDetourData(insertion, null, detourTimeInfo);
assertThat(insertionCostCalculator.calculate(drtRequest, insertionWithDetourData.insertion,
insertionWithDetourData.detourTimeInfo)).isEqualTo(expectedCost);
}

private VehicleEntry entry(double[] slackTimes) {
return new VehicleEntry(null, null, null, slackTimes, null, 0);
private VehicleEntry entry(double[] slackTimes, ImmutableList<Waypoint.Stop> stops, Waypoint.Start start) {
return new VehicleEntry(null, start, stops, slackTimes, stops.stream().map(s -> 0.).toList(), 0);
}

private Link link(String id) {
Expand Down

0 comments on commit 870a0f7

Please sign in to comment.