Skip to content

Commit

Permalink
add group passenger engine
Browse files Browse the repository at this point in the history
  • Loading branch information
nkuehnel committed Oct 13, 2023
1 parent fc1926b commit 28b90df
Show file tree
Hide file tree
Showing 6 changed files with 360 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package org.matsim.contrib.dvrp.passenger;

import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.Identifiable;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.population.Leg;
import org.matsim.api.core.v01.population.Route;
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.contrib.dvrp.optimizer.VrpOptimizer;
import org.matsim.contrib.dvrp.run.DvrpModes;
import org.matsim.core.api.experimental.events.EventsManager;
import org.matsim.core.gbl.Gbl;
import org.matsim.core.mobsim.framework.*;
import org.matsim.core.mobsim.qsim.InternalInterface;
import org.matsim.core.modal.ModalProviders;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors;

public class GroupPassengerEngine implements PassengerEngine, PassengerRequestRejectedEventHandler {

private final String mode;
private final MobsimTimer mobsimTimer;

private final PassengerRequestCreator requestCreator;
private final VrpOptimizer optimizer;
private final Network network;
private final PassengerRequestValidator requestValidator;

private final InternalPassengerHandling internalPassengerHandling;

private InternalInterface internalInterface;



//accessed in doSimStep() and handleDeparture() (no need to sync)
private final Map<Id<Request>, Set<MobsimPassengerAgent>> activePassengers = new HashMap<>();

//accessed in doSimStep() and handleEvent() (potential data races)
private final Queue<PassengerRequestRejectedEvent> rejectedRequestsEvents = new ConcurrentLinkedQueue<>();

private final Set<MobsimPassengerAgent> currentDepartures = new LinkedHashSet<>();

private final PassengerGroupIdentifier groupIdentifier;

GroupPassengerEngine(String mode, EventsManager eventsManager, MobsimTimer mobsimTimer,
PassengerRequestCreator requestCreator, VrpOptimizer optimizer, Network network,
PassengerRequestValidator requestValidator, PassengerGroupIdentifier groupIdentifier) {
this.mode = mode;
this.mobsimTimer = mobsimTimer;
this.requestCreator = requestCreator;
this.optimizer = optimizer;
this.network = network;
this.requestValidator = requestValidator;
this.groupIdentifier = groupIdentifier;

internalPassengerHandling = new InternalPassengerHandling(mode, eventsManager);
}

@Override
public void setInternalInterface(InternalInterface internalInterface) {
this.internalInterface = internalInterface;
internalPassengerHandling.setInternalInterface(internalInterface);
}

@Override
public void onPrepareSim() {
}

@Override
public void doSimStep(double time) {
handleDepartures();
while (!rejectedRequestsEvents.isEmpty()) {
Set<MobsimPassengerAgent> passengers = activePassengers.remove(rejectedRequestsEvents.poll().getRequestId());
//not much else can be done for immediate requests
//set the passenger agent to abort - the event will be thrown by the QSim
for (MobsimPassengerAgent passenger: passengers) {
passenger.setStateToAbort(mobsimTimer.getTimeOfDay());
internalInterface.arrangeNextAgentState(passenger);
}
}
}

@Override
public void afterSim() {
}

@Override
public boolean handleDeparture(double now, MobsimAgent agent, Id<Link> fromLinkId) {
if (!agent.getMode().equals(mode)) {
return false;
}

MobsimPassengerAgent passenger = (MobsimPassengerAgent)agent;
internalInterface.registerAdditionalAgentOnLink(passenger);
currentDepartures.add(passenger);
return true;
}

private void handleDepartures() {
Map<Id<PassengerGroupIdentifier.PassengerGroup>, List<MobsimPassengerAgent>> agentGroups
= currentDepartures.stream().collect(Collectors.groupingBy(groupIdentifier));

for (List<MobsimPassengerAgent> group : agentGroups.values()) {

Iterator<MobsimPassengerAgent> iterator = group.iterator();
MobsimAgent firstAgent = iterator.next();
Id<Link> toLinkId = firstAgent.getDestinationLinkId();
Route route = ((Leg) ((PlanAgent) firstAgent).getCurrentPlanElement()).getRoute();

while(iterator.hasNext()) {
MobsimAgent next = iterator.next();
Gbl.assertIf(firstAgent.getCurrentLinkId().equals(next.getCurrentLinkId()));
Gbl.assertIf(toLinkId.equals(next.getDestinationLinkId()));
}

PassengerRequest request = requestCreator.createRequest(internalPassengerHandling.createRequestId(),
group.stream().map(Identifiable::getId).toList(), route,
getLink(firstAgent.getCurrentLinkId()), getLink(toLinkId),
mobsimTimer.getTimeOfDay(), mobsimTimer.getTimeOfDay());
validateAndSubmitRequest(new HashSet<>(group), request, mobsimTimer.getTimeOfDay());
}

currentDepartures.clear();
}

private void validateAndSubmitRequest(Set<MobsimPassengerAgent> passengers, PassengerRequest request, double now) {
activePassengers.put(request.getId(), passengers);
if (internalPassengerHandling.validateRequest(request, requestValidator, now)) {
//need to synchronise to address cases where requestSubmitted() may:
// - be called from outside DepartureHandlers
// - interfere with VrpOptimizer.nextTask()
// - impact VrpAgentLogic.computeNextAction()
synchronized (optimizer) {
//optimizer can also reject request if cannot handle it
// (async operation, notification comes via the events channel)
optimizer.requestSubmitted(request);
}
}
}

private Link getLink(Id<Link> linkId) {
return Preconditions.checkNotNull(network.getLinks().get(linkId),
"Link id=%s does not exist in network for mode %s. Agent departs from a link that does not belong to that network?",
linkId, mode);
}

@Override
public boolean tryPickUpPassengers(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver,
Id<Request> requestId, double now) {
boolean pickedUp = internalPassengerHandling.tryPickUpPassengers(driver, activePassengers.get(requestId),
requestId, now);
Verify.verify(pickedUp, "Not possible without prebooking");
return pickedUp;
}

@Override
public void dropOffPassengers(MobsimDriverAgent driver, Id<Request> requestId, double now) {
internalPassengerHandling.dropOffPassengers(driver, activePassengers.remove(requestId), requestId, now);
}

@Override
public void handleEvent(PassengerRequestRejectedEvent event) {
if (event.getMode().equals(mode)) {
rejectedRequestsEvents.add(event);
}
}

public static Provider<PassengerEngine> createProvider(String mode) {
return new ModalProviders.AbstractProvider<>(mode, DvrpModes::mode) {
@Inject
private EventsManager eventsManager;

@Inject
private MobsimTimer mobsimTimer;

@Override
public GroupPassengerEngine get() {
return new GroupPassengerEngine(getMode(), eventsManager, mobsimTimer,
getModalInstance(PassengerRequestCreator.class), getModalInstance(VrpOptimizer.class),
getModalInstance(Network.class), getModalInstance(PassengerRequestValidator.class), getModalInstance(PassengerGroupIdentifier.class));
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
public class PassengerEngineQSimModule extends AbstractDvrpModeQSimModule {
public enum PassengerEngineType {
DEFAULT, WITH_PREBOOKING, TELEPORTING
DEFAULT, WITH_PREBOOKING, TELEPORTING, WITH_GROUPS
}

private final PassengerEngineType type;
Expand Down Expand Up @@ -44,6 +44,10 @@ protected void configureQSim() {
addModalComponent( PassengerEngine.class, TeleportingPassengerEngine.createProvider( getMode() ) );
return;
}
case WITH_GROUPS -> {
addModalComponent( PassengerEngine.class, GroupPassengerEngine.createProvider( getMode() ) );
return;
}
default -> throw new IllegalStateException( "Type: " + type + " is not supported" );
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.matsim.contrib.dvrp.passenger;

import org.matsim.api.core.v01.Id;
import org.matsim.core.mobsim.framework.MobsimPassengerAgent;

import java.util.function.Function;

/**
* Provides a method to identify the passenger group id of an agent.
* @author nkuehnel / MOIA
*/
public interface PassengerGroupIdentifier extends Function<MobsimPassengerAgent, Id<PassengerGroupIdentifier.PassengerGroup>> {

class PassengerGroup {
private PassengerGroup(){}
}

@Override
Id<PassengerGroupIdentifier.PassengerGroup> apply(MobsimPassengerAgent agent);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package org.matsim.contrib.dvrp.passenger;

import com.google.common.collect.ImmutableMap;
import org.apache.commons.compress.utils.Sets;
import org.junit.Test;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.events.*;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.dvrp.examples.onetaxi.OneTaxiActionCreator;
import org.matsim.contrib.dvrp.examples.onetaxi.OneTaxiOptimizer;
import org.matsim.contrib.dvrp.examples.onetaxi.OneTaxiRequest;
import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
import org.matsim.contrib.dvrp.fleet.DvrpVehicleImpl;
import org.matsim.contrib.dvrp.fleet.Fleet;
import org.matsim.contrib.dvrp.fleet.ImmutableDvrpVehicleSpecification;
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.contrib.dvrp.optimizer.VrpOptimizer;
import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule;
import org.matsim.contrib.dvrp.run.DvrpModes;
import org.matsim.contrib.dvrp.run.MobsimTimerProvider;
import org.matsim.contrib.dvrp.vrpagent.VrpAgentLogic;
import org.matsim.contrib.dvrp.vrpagent.VrpAgentSourceQSimModule;
import org.matsim.contrib.dynagent.run.DynActivityEngine;
import org.matsim.core.events.MobsimScopeEventHandlingModule;
import org.matsim.core.mobsim.framework.MobsimTimer;
import org.matsim.core.mobsim.qsim.QSim;
import org.matsim.core.mobsim.qsim.QSimBuilder;
import org.matsim.vehicles.VehicleType;
import org.matsim.vehicles.VehicleUtils;

import java.util.List;
import java.util.Set;

import static org.matsim.contrib.dvrp.passenger.PassengerEngineTestFixture.*;

public class GroupPassengerEngineTest {

private final PassengerEngineTestFixture fixture = new PassengerEngineTestFixture();

private final Id<DvrpVehicle> VEHICLE_ID = Id.create("taxi1", DvrpVehicle.class);
private final DvrpVehicle oneTaxi = new DvrpVehicleImpl(ImmutableDvrpVehicleSpecification.newBuilder()
.id(VEHICLE_ID)
.serviceBeginTime(0)
.serviceEndTime(3600)
.startLinkId(fixture.linkAB.getId())
.capacity(1)
.build(), fixture.linkAB);
private final Fleet fleet = () -> ImmutableMap.of(oneTaxi.getId(), oneTaxi);

@Test
public void test_group() {
double departureTime = 0;
Id<Person> person1 = Id.createPersonId("1");
Id<Person> person2 = Id.createPersonId("2");

fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime, person2);
fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime, person1);

PassengerRequestValidator requestValidator = request -> Set.of();//valid
createQSim(requestValidator, OneTaxiOptimizer.class).run();

double pickupStartTime = 1;
double pickupEndTime = pickupStartTime + OneTaxiOptimizer.PICKUP_DURATION;
double taxiDepartureTime = pickupEndTime + 1;
double taxiEntersLinkBATime = taxiDepartureTime + 1;
double taxiArrivalTime = taxiEntersLinkBATime + (fixture.linkBA.getLength() / fixture.linkBA.getFreespeed());
double dropoffEndTime = taxiArrivalTime + OneTaxiOptimizer.DROPOFF_DURATION;

//1 second delay between pickupEndTime and taxiDepartureTime is not considered in schedules
double scheduledDropoffTime = dropoffEndTime - pickupStartTime - 1;

var requestId = Id.create("taxi_0", Request.class);
fixture.assertPassengerEvents(
Sets.newHashSet(person1, person2),
new ActivityEndEvent(departureTime, person2, fixture.linkAB.getId(), null, START_ACTIVITY),
new PersonDepartureEvent(departureTime, person2, fixture.linkAB.getId(), MODE, MODE),
new ActivityEndEvent(departureTime, person1, fixture.linkAB.getId(), null, START_ACTIVITY),
new PersonDepartureEvent(departureTime, person1, fixture.linkAB.getId(), MODE, MODE),
new PassengerRequestScheduledEvent(departureTime, MODE, requestId, List.of(person1, person2), VEHICLE_ID, 0,
scheduledDropoffTime),
new PersonEntersVehicleEvent(pickupStartTime, person1, Id.createVehicleId(VEHICLE_ID)),
new PassengerPickedUpEvent(pickupStartTime, MODE, requestId, person1, VEHICLE_ID),
new PersonEntersVehicleEvent(pickupStartTime, person2, Id.createVehicleId(VEHICLE_ID)),
new PassengerPickedUpEvent(pickupStartTime, MODE, requestId, person2, VEHICLE_ID),
new PassengerDroppedOffEvent(dropoffEndTime, MODE, requestId, person1, VEHICLE_ID),
new PersonLeavesVehicleEvent(dropoffEndTime, person1, Id.createVehicleId(VEHICLE_ID)),
new PersonArrivalEvent(dropoffEndTime, person1, fixture.linkBA.getId(), MODE),
new ActivityStartEvent(dropoffEndTime, person1, fixture.linkBA.getId(), null, END_ACTIVITY),
new PassengerDroppedOffEvent(dropoffEndTime, MODE, requestId, person2, VEHICLE_ID),
new PersonLeavesVehicleEvent(dropoffEndTime, person2, Id.createVehicleId(VEHICLE_ID)),
new PersonArrivalEvent(dropoffEndTime, person2, fixture.linkBA.getId(), MODE),
new ActivityStartEvent(dropoffEndTime, person2, fixture.linkBA.getId(), null, END_ACTIVITY));
}


private QSim createQSim(PassengerRequestValidator requestValidator, Class<? extends VrpOptimizer> optimizerClass) {
return new QSimBuilder(fixture.config).useDefaults()
.addOverridingModule(new MobsimScopeEventHandlingModule())
.addQSimModule(new PassengerEngineQSimModule(MODE, PassengerEngineQSimModule.PassengerEngineType.WITH_GROUPS))
.addQSimModule(new VrpAgentSourceQSimModule(MODE))
.addQSimModule(new AbstractDvrpModeQSimModule(MODE) {
@Override
protected void configureQSim() {
bindModal(Network.class).toInstance(fixture.network);
bind(MobsimTimer.class).toProvider(MobsimTimerProvider.class).asEagerSingleton();

//requests
bindModal(PassengerRequestCreator.class).to(OneTaxiRequest.OneTaxiRequestCreator.class)
.asEagerSingleton();
bindModal(PassengerRequestValidator.class).toInstance(requestValidator);

//supply
addQSimComponentBinding(DynActivityEngine.COMPONENT_NAME).to(DynActivityEngine.class);
bindModal(Fleet.class).toInstance(fleet);
bindModal(VehicleType.class).toInstance(VehicleUtils.getDefaultVehicleType());
bindModal(VrpOptimizer.class).to(optimizerClass).asEagerSingleton();
bindModal(VrpAgentLogic.DynActionCreator.class).to(OneTaxiActionCreator.class)
.asEagerSingleton();

//groups
bindModal(PassengerGroupIdentifier.class).toInstance(agent -> Id.create("group1", PassengerGroupIdentifier.PassengerGroup.class));
}
})
.configureQSimComponents(components -> {
components.addComponent(DvrpModes.mode(MODE));
components.addNamedComponent(DynActivityEngine.COMPONENT_NAME);
})
.build(fixture.scenario, fixture.eventsManager);
}
}
Loading

0 comments on commit 28b90df

Please sign in to comment.