Skip to content

Commit 4b93368

Browse files
authored
feat: Intermodality between PT and DRT for access and egress (#206)
* feat: ActivityTourFinderWithExcludedActivities enabled in ile_de_france * fix: optional eqasim termination (#204) (#205) * feat: Optional EqasimTermination module * fix: making sure no EqasimTerminationModule is instantiated during RunPopulationRouting * fix: ModeShareModule is part of the Eqasim Termination * feat: optional config groups added in all run scripts * fix: imageio requiring vendor name and version in some settings * fix: bug in ActivityTourFinderWithExcludedActivities * chore: testing standalone mode choice IsolatedOutsideTrips tour filter * feat: base MultiModeFeederDrt simulation capability * feat: default integration of FeederDrt into the mode choice * feat: AdaptConfigForDrt * feat: Feeder Drt now supported by default in EqasimConfigurator * fix: problematic shuffle of mode names * fix: FeederDrtRoutingModule shouldn't be bound as a singleton * feat: testing feeder drt functionality * fix: drt analysis * feet: FeederDrtAnalysisModule * feat: configurable access egress transit stop modes * feat: fast failing when no transit stop is available for intermodality * refractor: MultiModeFeederDrtConfigGroup name in the xml * fix: empty accessEgressModeName not handled correctly * feat: DrtModeAvailabilityWrapper * feat: FeederDrtModeAvailabilityWrapper * fix: FeederDrtModeAvailabilityWrapper * feat: constraining switching between feeder and drt next to outside activities * feat: ensuring that feeder alternatives do not have drt segments next to outside activities * feat: access and egress stop selection is now behind an interface. ClosestAccessEgressStopSelector supports ignoring certain facilities. * chore: update doc with a page for DRT
1 parent e0231a8 commit 4b93368

32 files changed

+1669
-16
lines changed

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ such as for:
1717
- [Sao Paulo](https://github.com/eqasim-org/sao_paulo)
1818

1919
To understand how to set up a simulation and run it, please refer to the
20-
respective repositories. To cut out smaller parts of existing simulations
21-
check out how to use this repository to [create simulation cut-outs](docs/cutting.md).
20+
respective repositories.
21+
22+
Additional topics:
23+
- How to [cut out smaller parts of existing simulations](docs/cutting.md).
24+
- How to [run a simulation with on-demand mobility services](docs/on_demand_mobility.md) (as a main mode and as a transit feeder).
2225

2326
## Main reference
2427

core/src/main/java/org/eqasim/core/simulation/EqasimConfigurator.java

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import org.eqasim.core.components.transit.EqasimTransitModule;
1717
import org.eqasim.core.components.transit.EqasimTransitQSimModule;
1818
import org.eqasim.core.simulation.mode_choice.epsilon.EpsilonModule;
19+
import org.eqasim.core.simulation.modes.feeder_drt.MultiModeFeederDrtModule;
20+
import org.eqasim.core.simulation.modes.feeder_drt.config.MultiModeFeederDrtConfigGroup;
21+
import org.eqasim.core.simulation.modes.feeder_drt.mode_choice.EqasimFeederDrtModeChoiceModule;
1922
import org.eqasim.core.simulation.termination.EqasimTerminationConfigGroup;
2023
import org.eqasim.core.simulation.termination.EqasimTerminationModule;
2124
import org.eqasim.core.simulation.termination.mode_share.ModeShareModule;
@@ -80,6 +83,7 @@ public EqasimConfigurator() {
8083

8184
this.registerOptionalConfigGroup(new DvrpConfigGroup(), Collections.singleton(new DvrpModule()));
8285
this.registerOptionalConfigGroup(new EqasimTerminationConfigGroup(), List.of(new EqasimTerminationModule(), new ModeShareModule()));
86+
this.registerOptionalConfigGroup(new MultiModeFeederDrtConfigGroup(), List.of(new MultiModeFeederDrtModule(), new EqasimFeederDrtModeChoiceModule()));
8387
}
8488

8589
public ConfigGroup[] getConfigGroups() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package org.eqasim.core.simulation.mode_choice.tour_finder;
2+
3+
import org.matsim.contribs.discrete_mode_choice.components.tour_finder.ActivityTourFinder;
4+
import org.matsim.contribs.discrete_mode_choice.components.tour_finder.TourFinder;
5+
import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip;
6+
7+
import java.util.*;
8+
import java.util.stream.Collectors;
9+
10+
11+
/**
12+
* This tour finder works similarly to the ActivityTourFinder with the difference that it allows to build tours guaranteeing that activities of certain types are excluded, thus preventing trips related to these activities from being considered in the mode choice.
13+
* This tour finder relies on a delegate tour finder to first build a set of tours. Then a process is applied on those tours to further split them in sequences of trips not containing trips related to excluded activities and sequences containing only such trips.
14+
* The typical use case for this tour finder is to exclude trips related to 'outside' activities from being altered.
15+
* This can be done by using the {@link org.matsim.contribs.discrete_mode_choice.components.tour_finder.ActivityTourFinder} with activityTypes={home, outside} as a delegate tour finder and 'outside' as an excluded activity type.
16+
* Using the above construction on the plan 'home -> leisure -> outside -> work -> shop -> home' will produce the following 'tours' (separated with ;)
17+
* home -> leisure; leisure -> outside; outside -> work; work -> shop -> hope.
18+
* Using the {@link org.eqasim.core.simulation.mode_choice.filters.OutsideFilter} then allows to consider the first and last 'tours' for mode choice.
19+
* This then allows to consider more trips in the mode choice than when using the base ActivityTourFinder coupled with the OutsideTourFilter which will divide the previous plan
20+
* into the two 'tours' home -> leisure -> outside and outside -> work -> shop -> home and filter both of them out.
21+
*/
22+
public class ActivityTourFinderWithExcludedActivities implements TourFinder {
23+
24+
private final Set<String> excludedActivityTypes;
25+
private final ActivityTourFinder delegate;
26+
27+
public ActivityTourFinderWithExcludedActivities(Collection<String> excludedActivityTypes, ActivityTourFinder delegate) {
28+
this.excludedActivityTypes = new HashSet<>(excludedActivityTypes);
29+
this.delegate = delegate;
30+
}
31+
32+
@Override
33+
public List<List<DiscreteModeChoiceTrip>> findTours(List<DiscreteModeChoiceTrip> trips) {
34+
List<List<DiscreteModeChoiceTrip>> baseTours = this.delegate.findTours(trips);
35+
return baseTours.stream().flatMap(tour -> this.isolateActivityTrips(tour).stream()).collect(Collectors.toList());
36+
}
37+
38+
private List<List<DiscreteModeChoiceTrip>> isolateActivityTrips(List<DiscreteModeChoiceTrip> trips) {
39+
List<List<DiscreteModeChoiceTrip>> tours = new ArrayList<>();
40+
41+
boolean isExcluding;
42+
boolean wasExcluding = this.isTripExcluded(trips.get(0));
43+
44+
List<DiscreteModeChoiceTrip> currentTour = new ArrayList<>();
45+
46+
for(int i=0; i<trips.size(); i++) {
47+
DiscreteModeChoiceTrip currentTrip = trips.get(i);
48+
if (i>0) {
49+
isExcluding = this.isTripExcluded(currentTrip);
50+
if(isExcluding != wasExcluding) {
51+
tours.add(currentTour);
52+
wasExcluding = isExcluding;
53+
currentTour = new ArrayList<>();
54+
}
55+
}
56+
currentTour.add(trips.get(i));
57+
}
58+
59+
if(currentTour.size() > 0) {
60+
tours.add(currentTour);
61+
}
62+
return tours;
63+
}
64+
65+
private boolean isTripExcluded(DiscreteModeChoiceTrip trip) {
66+
return this.excludedActivityTypes.contains(trip.getOriginActivity().getType()) || this.excludedActivityTypes.contains(trip.getDestinationActivity().getType());
67+
}
68+
}

core/src/main/java/org/eqasim/core/simulation/modes/drt/analysis/dvrp_vehicles/VehicleAnalysisListener.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.matsim.api.core.v01.Id;
77
import org.matsim.api.core.v01.events.*;
88
import org.matsim.api.core.v01.events.handler.*;
9+
import org.matsim.contrib.dvrp.vrpagent.VrpAgentLogic;
910
import org.matsim.vehicles.Vehicle;
1011

1112
import java.util.HashMap;
@@ -100,7 +101,7 @@ public void handleEvent(PersonArrivalEvent event) {
100101

101102
@Override
102103
public void handleEvent(ActivityStartEvent event) {
103-
if (vehicleRegistry.isFleet(event.getPersonId())) {
104+
if (this.vehicleRegistry.isFleet(event.getPersonId()) && !VrpAgentLogic.BEFORE_SCHEDULE_ACTIVITY_TYPE.equals(event.getActType()) && !VrpAgentLogic.AFTER_SCHEDULE_ACTIVITY_TYPE.equals(event.getActType())) {
104105
String mode = vehicleRegistry.getMode(event.getPersonId());
105106
Id<Vehicle> vehicleId = Id.createVehicleId(event.getPersonId());
106107

@@ -121,10 +122,7 @@ public void handleEvent(ActivityStartEvent event) {
121122

122123
@Override
123124
public void handleEvent(ActivityEndEvent event) {
124-
// Here we want to skip activity type 'BeforeVrpSchedule'
125-
// Since the current version of the VehicleRegistry considers the vehicle to be part of the fleet only after the TaskStartedEvent
126-
// It is safe to just check the vehicle against the fleet here.
127-
if (vehicleRegistry.isFleet(event.getPersonId())) {
125+
if (this.vehicleRegistry.isFleet(event.getPersonId()) && !VrpAgentLogic.BEFORE_SCHEDULE_ACTIVITY_TYPE.equals(event.getActType()) && !VrpAgentLogic.AFTER_SCHEDULE_ACTIVITY_TYPE.equals(event.getActType())) {
128126
String mode = vehicleRegistry.getMode(event.getPersonId());
129127
Id<Vehicle> vehicleId = Id.createVehicleId(event.getPersonId());
130128

core/src/main/java/org/eqasim/core/simulation/modes/drt/analysis/run/RunPassengerAnalysis.java renamed to core/src/main/java/org/eqasim/core/simulation/modes/drt/analysis/run/RunDrtPassengerAnalysis.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
import org.eqasim.core.simulation.modes.drt.analysis.utils.LinkFinder;
66
import org.eqasim.core.simulation.modes.drt.analysis.utils.VehicleRegistry;
77
import org.matsim.api.core.v01.network.Network;
8+
import org.matsim.contrib.drt.util.DrtEventsReaders;
89
import org.matsim.core.api.experimental.events.EventsManager;
910
import org.matsim.core.config.CommandLine;
1011
import org.matsim.core.config.CommandLine.ConfigurationException;
1112
import org.matsim.core.events.EventsUtils;
12-
import org.matsim.core.events.MatsimEventsReader;
1313
import org.matsim.core.network.NetworkUtils;
1414
import org.matsim.core.network.io.MatsimNetworkReader;
1515

@@ -19,7 +19,7 @@
1919
import java.util.Set;
2020
import java.util.stream.Collectors;
2121

22-
public class RunPassengerAnalysis {
22+
public class RunDrtPassengerAnalysis {
2323
static public void main(String[] args) throws ConfigurationException, IOException {
2424
CommandLine cmd = new CommandLine.Builder(args) //
2525
.requireOptions("events-path", "network-path", "output-path", "modes") //
@@ -44,7 +44,7 @@ static public void main(String[] args) throws ConfigurationException, IOExceptio
4444
eventsManager.addHandler(listener);
4545

4646
eventsManager.initProcessing();
47-
new MatsimEventsReader(eventsManager).readFile(eventsPath);
47+
DrtEventsReaders.createEventsReader(eventsManager).readFile(eventsPath);
4848
eventsManager.finishProcessing();
4949

5050
new PassengerAnalysisWriter(listener).writeRides(new File(outputPath));

core/src/main/java/org/eqasim/core/simulation/modes/drt/analysis/run/RunDvrpVehicleAnalysis.java renamed to core/src/main/java/org/eqasim/core/simulation/modes/drt/analysis/run/RunDrtVehicleAnalysis.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.eqasim.core.simulation.modes.drt.analysis.utils.LinkFinder;
66
import org.eqasim.core.simulation.modes.drt.analysis.utils.VehicleRegistry;
77
import org.matsim.api.core.v01.network.Network;
8+
import org.matsim.contrib.drt.util.DrtEventsReaders;
89
import org.matsim.core.api.experimental.events.EventsManager;
910
import org.matsim.core.config.CommandLine;
1011
import org.matsim.core.config.CommandLine.ConfigurationException;
@@ -16,7 +17,7 @@
1617
import java.io.File;
1718
import java.io.IOException;
1819

19-
public class RunDvrpVehicleAnalysis {
20+
public class RunDrtVehicleAnalysis {
2021
static public void main(String[] args) throws ConfigurationException, IOException {
2122
CommandLine cmd = new CommandLine.Builder(args) //
2223
.requireOptions("events-path", "network-path", "movements-output-path", "activities-output-path") //
@@ -39,7 +40,7 @@ static public void main(String[] args) throws ConfigurationException, IOExceptio
3940
eventsManager.addHandler(listener);
4041

4142
eventsManager.initProcessing();
42-
new MatsimEventsReader(eventsManager).readFile(eventsPath);
43+
DrtEventsReaders.createEventsReader(eventsManager).readFile(eventsPath);
4344
eventsManager.finishProcessing();
4445

4546
new VehicleAnalysisWriter(listener).writeMovements(new File(movementsOutputPath));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.eqasim.core.simulation.modes.drt.mode_choice;
2+
3+
import org.matsim.api.core.v01.population.Person;
4+
import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
5+
import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip;
6+
import org.matsim.contribs.discrete_mode_choice.model.mode_availability.ModeAvailability;
7+
8+
import java.util.Collection;
9+
import java.util.List;
10+
11+
public class DrtModeAvailabilityWrapper implements ModeAvailability {
12+
private final Collection<String> drtModes;
13+
private final ModeAvailability delegate;
14+
15+
public DrtModeAvailabilityWrapper(MultiModeDrtConfigGroup multiModeDrtConfigGroup, ModeAvailability delegate) {
16+
this.drtModes = multiModeDrtConfigGroup.modes().toList();
17+
this.delegate = delegate;
18+
}
19+
20+
@Override
21+
public Collection<String> getAvailableModes(Person person, List<DiscreteModeChoiceTrip> trips) {
22+
Collection<String> modes = this.delegate.getAvailableModes(person, trips);
23+
modes.addAll(this.drtModes);
24+
return modes;
25+
}
26+
}

core/src/main/java/org/eqasim/core/simulation/modes/drt/utils/AdaptConfigForDrt.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public static void adapt(Config config, Map<String, String> vehiclesPathByDrtMod
100100

101101
}
102102

103-
private static Map<String, Map<String, String>> extractDrtInfo(String[] drtModeNames, Map<String, String[]> values) {
103+
public static Map<String, Map<String, String>> extractDrtInfo(String[] drtModeNames, Map<String, String[]> values) {
104104
Map<String, Map<String, String>> result = new HashMap<>();
105105
if(drtModeNames.length == 0) {
106106
throw new IllegalStateException("No drt modes provided");
@@ -137,7 +137,7 @@ public static void main(String[] args) throws CommandLine.ConfigurationException
137137

138138
String inputConfigPath = cmd.getOptionStrict("input-config-path");
139139
String outputConfigPath = cmd.getOptionStrict("output-config-path");
140-
String[] modeNames = Arrays.stream(cmd.getOption("mode-names").orElse("drt").split(",")).collect(Collectors.toSet()).toArray(String[]::new);
140+
String[] modeNames = Arrays.stream(cmd.getOption("mode-names").orElse("drt").split(",")).toList().toArray(String[]::new);
141141
String[] vehiclesPath = cmd.getOptionStrict("vehicles-paths").split(",");
142142
String qsimEndtime = cmd.getOption("qsim-endtime").orElse("30:00:00");
143143
String[] costModel = cmd.getOption("cost-models").orElse(EqasimModeChoiceModule.ZERO_COST_MODEL_NAME).split(",");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.eqasim.core.simulation.modes.feeder_drt;
2+
3+
import com.google.inject.Inject;
4+
import com.google.inject.Injector;
5+
import com.google.inject.Provider;
6+
import org.eqasim.core.simulation.modes.feeder_drt.config.AccessEgressStopSelectorParams;
7+
import org.eqasim.core.simulation.modes.feeder_drt.config.FeederDrtConfigGroup;
8+
import org.eqasim.core.simulation.modes.feeder_drt.router.FeederDrtRoutingModule;
9+
import org.eqasim.core.simulation.modes.feeder_drt.router.access_egress_selector.AccessEgressStopsSelector;
10+
import org.eqasim.core.simulation.modes.feeder_drt.router.access_egress_selector.ClosestAccessEgressStopSelector;
11+
import org.eqasim.core.simulation.modes.feeder_drt.router.access_egress_selector.ClosestAccessEgressStopSelectorParameterSet;
12+
import org.matsim.api.core.v01.network.Network;
13+
import org.matsim.api.core.v01.population.Population;
14+
import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule;
15+
import org.matsim.contrib.dvrp.run.DvrpMode;
16+
import org.matsim.contrib.dvrp.run.DvrpModes;
17+
import org.matsim.core.config.ReflectiveConfigGroup;
18+
import org.matsim.core.modal.ModalAnnotationCreator;
19+
import org.matsim.core.router.RoutingModule;
20+
import org.matsim.pt.transitSchedule.api.TransitSchedule;
21+
22+
import java.util.Map;
23+
24+
25+
public class FeederDrtModeModule extends AbstractDvrpModeModule {
26+
27+
private final FeederDrtConfigGroup config;
28+
29+
public FeederDrtModeModule(FeederDrtConfigGroup feederDrtConfigGroup) {
30+
super(feederDrtConfigGroup.mode);
31+
this.config = feederDrtConfigGroup;
32+
}
33+
34+
35+
@Override
36+
public void install() {
37+
FeederDrtConfigGroup feederDrtConfigGroup = this.config;
38+
ReflectiveConfigGroup reflectiveConfigGroup = feederDrtConfigGroup.getAccessEgressStopSelectorConfig().getAccessEgressStopSelectorParams();
39+
if(reflectiveConfigGroup instanceof ClosestAccessEgressStopSelectorParameterSet closestAccessEgressStopSelectorConfig) {
40+
bindModal(AccessEgressStopsSelector.class).toProvider(new Provider<>() {
41+
@Inject
42+
private Injector injector;
43+
44+
@Inject
45+
private TransitSchedule transitSchedule;
46+
47+
@Override
48+
public AccessEgressStopsSelector get() {
49+
ModalAnnotationCreator<DvrpMode> modalAnnotationCreator = DvrpModes::mode;
50+
Provider<Network> networkProvider = injector.getProvider(modalAnnotationCreator.key(Network.class, feederDrtConfigGroup.accessEgressModeName));
51+
return new ClosestAccessEgressStopSelector(closestAccessEgressStopSelectorConfig, networkProvider.get(), transitSchedule);
52+
}
53+
}).asEagerSingleton();
54+
} else {
55+
throw new RuntimeException(String.format("Unsupported %s: %s", AccessEgressStopSelectorParams.NAME, reflectiveConfigGroup));
56+
}
57+
58+
addRoutingModuleBinding(this.config.mode).toProvider(new Provider<>() {
59+
@Inject
60+
private Map<String, Provider<RoutingModule>> routingModuleProviders;
61+
62+
@Inject
63+
private Injector injector;
64+
65+
@Inject
66+
private Population population;
67+
68+
@Override
69+
public RoutingModule get() {
70+
RoutingModule ptRoutingModule = routingModuleProviders.get(feederDrtConfigGroup.ptModeName).get();
71+
RoutingModule drtRoutingModule = routingModuleProviders.get(feederDrtConfigGroup.accessEgressModeName).get();
72+
ModalAnnotationCreator<DvrpMode> modalAnnotationCreator = DvrpModes::mode;
73+
Provider<AccessEgressStopsSelector> accessEgressStopsSelectorProvider = injector.getProvider(modalAnnotationCreator.key(AccessEgressStopsSelector.class, feederDrtConfigGroup.mode));
74+
return new FeederDrtRoutingModule(feederDrtConfigGroup.mode, drtRoutingModule, ptRoutingModule, population.getFactory(), accessEgressStopsSelectorProvider.get());
75+
}
76+
});
77+
}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.eqasim.core.simulation.modes.feeder_drt;
2+
3+
import com.google.inject.Inject;
4+
import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension;
5+
import org.eqasim.core.simulation.modes.feeder_drt.analysis.FeederDrtAnalysisModule;
6+
import org.eqasim.core.simulation.modes.feeder_drt.config.FeederDrtConfigGroup;
7+
import org.eqasim.core.simulation.modes.feeder_drt.config.MultiModeFeederDrtConfigGroup;
8+
9+
public class MultiModeFeederDrtModule extends AbstractEqasimExtension {
10+
@Inject
11+
MultiModeFeederDrtConfigGroup multiModeFeederDrtConfigGroup;
12+
13+
@Override
14+
protected void installEqasimExtension() {
15+
for(FeederDrtConfigGroup feederDrtConfigGroup: this.multiModeFeederDrtConfigGroup.getModalElements()) {
16+
install(new FeederDrtModeModule(feederDrtConfigGroup));
17+
}
18+
if(multiModeFeederDrtConfigGroup.performAnalysis) {
19+
install(new FeederDrtAnalysisModule());
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)