From 28b90df72261d0c596a793f680c5a6f0dfa2a162 Mon Sep 17 00:00:00 2001 From: nkuehnel Date: Fri, 13 Oct 2023 19:50:25 +0200 Subject: [PATCH] add group passenger engine --- .../dvrp/passenger/GroupPassengerEngine.java | 191 ++++++++++++++++++ .../passenger/PassengerEngineQSimModule.java | 6 +- .../passenger/PassengerGroupIdentifier.java | 21 ++ .../passenger/GroupPassengerEngineTest.java | 131 ++++++++++++ .../passenger/PassengerEngineTestFixture.java | 9 +- .../TeleportingPassengerEngineTest.java | 11 +- 6 files changed, 360 insertions(+), 9 deletions(-) create mode 100644 contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngine.java create mode 100644 contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerGroupIdentifier.java create mode 100644 contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngineTest.java diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngine.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngine.java new file mode 100644 index 00000000000..42e339771bc --- /dev/null +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngine.java @@ -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, Set> activePassengers = new HashMap<>(); + + //accessed in doSimStep() and handleEvent() (potential data races) + private final Queue rejectedRequestsEvents = new ConcurrentLinkedQueue<>(); + + private final Set 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 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 fromLinkId) { + if (!agent.getMode().equals(mode)) { + return false; + } + + MobsimPassengerAgent passenger = (MobsimPassengerAgent)agent; + internalInterface.registerAdditionalAgentOnLink(passenger); + currentDepartures.add(passenger); + return true; + } + + private void handleDepartures() { + Map, List> agentGroups + = currentDepartures.stream().collect(Collectors.groupingBy(groupIdentifier)); + + for (List group : agentGroups.values()) { + + Iterator iterator = group.iterator(); + MobsimAgent firstAgent = iterator.next(); + Id 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 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 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 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 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 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)); + } + }; + } +} diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineQSimModule.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineQSimModule.java index 6f9517c5153..e9d806af179 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineQSimModule.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineQSimModule.java @@ -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; @@ -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" ); } } diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerGroupIdentifier.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerGroupIdentifier.java new file mode 100644 index 00000000000..8dba2bdac52 --- /dev/null +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerGroupIdentifier.java @@ -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> { + + class PassengerGroup { + private PassengerGroup(){} + } + + @Override + Id apply(MobsimPassengerAgent agent); + +} diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngineTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngineTest.java new file mode 100644 index 00000000000..f594b34648b --- /dev/null +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngineTest.java @@ -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 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 person1 = Id.createPersonId("1"); + Id 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 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); + } +} diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/PassengerEngineTestFixture.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/PassengerEngineTestFixture.java index a68e20e6157..1f83607037f 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/PassengerEngineTestFixture.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/PassengerEngineTestFixture.java @@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.matsim.api.core.v01.Coord; @@ -81,7 +82,7 @@ public PassengerEngineTestFixture() { eventsManager.initProcessing(); } - void addPersonWithLeg(Link fromLink, Link toLink, double departureTime) { + void addPersonWithLeg(Link fromLink, Link toLink, double departureTime, Id person_id) { PopulationFactory factory = scenario.getPopulation().getFactory(); Plan plan = factory.createPlan(); @@ -99,16 +100,16 @@ void addPersonWithLeg(Link fromLink, Link toLink, double departureTime) { plan.addActivity(factory.createActivityFromLinkId(END_ACTIVITY, toLink.getId())); - Person person = factory.createPerson(PERSON_ID); + Person person = factory.createPerson(person_id); person.addPlan(plan); scenario.getPopulation().addPerson(person); } - void assertPassengerEvents(Event... events) { + void assertPassengerEvents(Collection> personIds, Event... events) { assertThat(recordedEvents.size()).isGreaterThanOrEqualTo(events.length); var recordedPassengerEvents = recordedEvents.stream() .filter(e -> - e instanceof HasPersonId && ((HasPersonId)e).getPersonId().equals(PERSON_ID) || + e instanceof HasPersonId && personIds.contains(((HasPersonId)e).getPersonId()) || e instanceof AbstractPassengerRequestEvent ); assertThat(recordedPassengerEvents).usingRecursiveFieldByFieldElementComparator().containsExactly(events); diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java index 84aa7850433..e13630be2f7 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java @@ -38,6 +38,7 @@ import org.matsim.core.population.routes.GenericRouteImpl; import java.util.Collections; +import java.util.List; import java.util.Set; import static org.matsim.contrib.dvrp.passenger.PassengerEngineTestFixture.*; @@ -51,7 +52,7 @@ public class TeleportingPassengerEngineTest { @Test public void test_valid_teleported() { double departureTime = 0; - fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime); + fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime, fixture.PERSON_ID); double travelTime = 999; double travelDistance = 555; @@ -67,9 +68,10 @@ public void test_valid_teleported() { double arrivalTime = departureTime + travelTime; var requestId = Id.create("taxi_0", Request.class); fixture.assertPassengerEvents( + Collections.singleton(fixture.PERSON_ID), new ActivityEndEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), - new PassengerRequestScheduledEvent(departureTime, MODE, requestId, Collections.singleton(fixture.PERSON_ID), null, departureTime, + new PassengerRequestScheduledEvent(departureTime, MODE, requestId, List.of(fixture.PERSON_ID), null, departureTime, arrivalTime), new PassengerPickedUpEvent(departureTime, MODE, requestId, fixture.PERSON_ID, null), new PassengerDroppedOffEvent(arrivalTime, MODE, requestId, fixture.PERSON_ID, null), new TeleportationArrivalEvent(arrivalTime, fixture.PERSON_ID, travelDistance, MODE), @@ -80,7 +82,7 @@ public void test_valid_teleported() { @Test public void test_invalid_rejected() { double departureTime = 0; - fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime); + fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime, fixture.PERSON_ID); TeleportedRouteCalculator teleportedRouteCalculator = request -> null; // unused PassengerRequestValidator requestValidator = request -> Set.of("invalid"); @@ -88,9 +90,10 @@ public void test_invalid_rejected() { var requestId = Id.create("taxi_0", Request.class); fixture.assertPassengerEvents( + Collections.singleton(fixture.PERSON_ID), new ActivityEndEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), - new PassengerRequestRejectedEvent(departureTime, MODE, requestId, Collections.singleton(fixture.PERSON_ID), "invalid"), + new PassengerRequestRejectedEvent(departureTime, MODE, requestId, List.of(fixture.PERSON_ID), "invalid"), new PersonStuckEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), MODE)); }